Writing efficient Functions in ILNumerics
1. Array Types for Function Declarations
ILNumerics provides distinct array types - regarding the content and regarding the lifetime. Usually, the user only handles common local arrays, like
However, in the need for an efficient memory management we added certain array types which are used only in the parameter list of function declarations:
- "Input arrays" are used as general, readonly, input parameter to a function.
- "Output arrays" serve as additional, optional, output parameter of a function.
- "Return arrays" are always used as return value of a function.
All array types are easily distinguishable by name. The general naming scheme is as follows:
Lets assume, a function is to receive two dense matrices as input parameter and returns a cell array. The function signature may looks as follows:
| C# Code | |
1 | static ILRetCell MyFunc(ILInArray<double> inA, ILInArray<double> inB) { ... |
The following table lists all array types existing in ILNumerics and describes their lifetimes:
| Content | Local Array | Input Parameter | Output Parameter | Return Value |
|---|---|---|---|---|
| Dense Array |
|
|
|
|
| Cell Array |
|
|
|
|
| Logical Array |
|
|
|
|
| Lifetimes | ||||
| Used in function .. | body | head | head | head |
| Disposed automatically ... | when current scope was left | when function scope was left | when calling scope was left | after the first use |
| Mutability | full mutable | immutable | full mutable | immutable |
| Functionality | ||||
| Assignments to variables of that type: |
A = ... | inA = ... [1] | outA.a = ... | (no variables)[2] |
[1] Not recommended for best performance, see: Optimizing Performance for details. [2] Return values are volatile and converted to other array types or immediately disposed after first use.
All those array types share the same set of functionality. They provide the same set of member functions for read access and can arbitrarily be given to any ILNumerics function as parameter. ILNumerics handles implicit conversions between compatible types automatically. The only exception: Input arrays and return arrays are immutable. This means, they lag of any member function or setter property which could be used to alter the content of the array. Also, these types cannot be converted to output parameters.
2. using() blocks as artificial scopes
Function bodies of any computational function must be enclosed with a construct according to the following scheme:
| C# Code | |
1 2 3 4 | using(ILScope.Enter(in1,in2,...)) { // function body... } |
In Visual Basic, a corresponding
The
- All arrays created inside the scope are disposed once the block was left.
- Input parameters are disposed after the block was left.
Therefore, the
3. Output parameters
Output parameters are used whenever a function must return more than one array. In ILNumerics, output parameters act like a reference to another (local) array in the calling scope. If a function receives a valid object reference for an output parameter (i.e. an
Modifications and assignments to output parameters immediately alter the underlying source array. In order to make this work, output parameters can be modified in one of the following ways only. Consider an output variable named
-
frequencies[0,full] = ... - use the setter properties to alter parts of the array. All subarray access rules are permitted. Alternatively, theSetRange() method may be used if the language used does not support those properties. -
frequencies.a = ... - reassign a new array to the output parameter. It is important not to forget to use the.a setter property. For languages which do not support such properties (f.e. Visual Basic .NET) thefrequencies.Assign() method of an array is to be used as alternative.
By using output parameters in ILNumerics, neither the
Example of a computational function in ILNumerics
Note, that in the body only local array types are declared - all other types go in the function head. Note also, the parameter list may just as well contain any parameter type useful for the function. In the example, a parameter of system type
Best Practices for Writing ILNumerics Functions
Next to the obligatory rules, the following hints are optional. The user may decide to use them in order to create more readable and convenient code.
- All parameters - where applicable - should be defined as optional parameters (see: C# language reference) at the end of the parameter list. Default value for all array parameters should be
null . This allows the user to pick only those output parameters, which are really needed in long parameter lists.
C# Code 1 2 3 4 5 6 7 8 9 10 11 12
public static YourFunc(ILInArray<double> inA, ILOutArray<double> outB = null) { // ... } // ... allowed use of the function: ILArray<double> I = zeros(5,4); ILArray<double> O = empty(); // request the additional output value: YourFunc(I,O); // do not request the additional output value: YourFunc(I);
- Prepend parameter names with corresponding prefixes 'in' and 'out'. This makes parameters easily distinguishable from local variables.
- Input paramters are immutable - it is impossible to change their content. In the context of input parameter checking, the following scheme has proved to be efficient:
- For every input parameter, a corresponding local parameter should be declared.
- The
check() function from theILMath class is convenient to check against any parameter beingnull and to define constraints for them. Input parameters are therefore transformed into their corresponding local variables, including all neccessary parameter checks. - Optimally, the check function would be the only place to reference any input parameter in the function body.
Example of efficient parameter checking in ILNumerics:
C# Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public static void MyFunc(ILInArray<double> inA, ILInArray<double> inB, ILInArray<double> inC) { using (ILScope.Enter(inA, inB, inC)) { // declare local arrays for all input parameters ILArray<double> A,B,C; // parameter checking via "check" function: A = check(inA); // checks on null B = check(inB, (b) => { // checks on column vector also if (!b.IsVector) throw new ILArgumentException("inB must be a vector!"); return (b.IsRowVector) ? b.C : b.T; }); // parameter checking the classical way: if (isnull(inC) || inC.IsEmpty || !inC.IsColumnVector || inC.Length != inB.D[0]) throw new ILArgumentException("invalid parameter inC provided"); // proceed with local variables A,B and C... // ... } }
By following these hints, assignments to input parameters are avoided completely. By doing so, ILNumerics is able to optimize memory usage furthermore. See Optimizing Performance for details.