ObjexxFCL 4.2 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ArrayThe Array class template hierarchy provides Fortran-style arrays with these attributes of Fortran 77 arrays:
and these capabilities not provided by Fortran 77 arrays:
The Arrays are not intended to be a complete numerical matrix class library. A number of general purpose matrix libraries are available for C++ and Objexx Engineering can provide interfacing to those libraries. Objexx Engineering can also provide linear algebra functions and solvers for the Array classes to meet project requirements. There are a few types of Arrays:
The Arrays are class templates. Shorthand (typedef) names are provided for the common value types in the Array forward declaration headers so, for example, we use Array2D_int instead of Array2D< int > on this page. Index RangesAs in Fortran arrays, the dimensions of Arrays can be an arbitrary index range. These ranges are described in the Index Ranges section. Each Array class contains a typedef named IR for IndexRange. SubscriptingArray elements can be accessed, as in Fortran, by passing a list of indices:
This type of subscripting can add significant overhead in inner loop computations, especially for higher rank arrays. For faster access to array elements when the elements are contiguous or the stride is known the Arrays provide an index member function that returns a zero-based linear index for subscripting via a C-style [] operator, as in:
The index calls return an unsigned value of type ArrayND<type>::size_type but they can be assigned to a signed or smaller integer type if there are no concerns about exceeding the range of that type. To support index math, index doesn't check that the subscript is in bounds for the Array but linear subscripting accesses to an Array's elements are bounds-checked via assertions in a debug build: these checks can only determine if the index is within the linear extent of the data array, so the index range checking of each dimension is sacrificed for speed. AssignmentArrays support the whole-array assignment of any Array of the same rank and dimensions with the operators {=, +=, -=}:
and the whole-array assignment of any value that is assignment-compatible with the Array's value type with the operators {=, +=, -=, *=, /=}:
The Array whole-array assignment operators are supported for value types that support those operators. Arrays also support assignment of different dimensioned arrays of the same rank with the operator = functions:
This will redimension the array if necessary before assigning the values of the other array. For an argument Array this redimensioning only changes the view into the data array to which it is attached. Individual Array elements can be assigned in the usual fashion using multidimensional or linear subscripting:
Warning!: Assignment between real Arrays and argument Array proxies of them or between proxies of the same real Array can lead to unexpected results and should be used with care. Base ArraysBase Arrays are the abstract base class for the concrete Arrays of the same rank and value type. Base Arrays classes have names of the form ArrayN<type> with shorthand names of the form ArrayN_type where N is the rank of the array and type is the value type the array hold. Functions written with pass by reference or const reference base Array arguments can be passed any Arrays of its rank and value type. Real ArraysReal Arrays are normal arrays that own their data. Real Arrays have names of the form ArrayND<type> with shorthand names of the form ArrayND_type where N is the rank of the array and type is the value type the array hold. ConstructionReal Arrays can be constructed in a number of ways. Default construction is not possible with Fortran 77 arrays but is for Arrays:
Default constructed real Arrays can be later dimensioned and allocated with a dimension call as described below. Index range construction is analogous to Fortran 77 array declaration:
To specify an index range with a lower index other than 1 an explicit index range must be specified, as in Fortran. The {-10,10} index range notation is a convenient syntax that gets converted to IndexRange( -10, 10 ) within the constructor. Copy constructors accept any Array of the same rank that has an data type that is assignable to the Array being constructed:
InitializationAs in Fortran, for run-time efficiency arrays are uninitialized by default. An optional final initializer constructor argument can be used to specify a uniform initializer value:
Or an initializer function for non-uniform initialization:
Initializer functions are any void functions that take a non-const reference to the Array type. Initializer values should be of the exact value type of the Array to avoid any potential C++ ambiguity when the value is zero of a different type that could be equally well converted to the value type or to a function pointer. Sticky initializers are automatically reapplied when a real Array is resized. Explicit resizing with a dimension call (see below) will remove the initializer but a variant dimension call with an extra initializer argument can be used. Data-preserving redimension calls (see below) also remove the initializer. The intializer value or function can be explicitly set with initializer calls and cleared with initializer_clear:
Arrays without initializer values or functions can be default initialized to the default-constructed value of the data type (zero for numeric types) by defining the OBJEXXFCL_ARRAY_INIT macro when compiling (this is done with a -DOBJEXXFCL_ARRAY_INIT switch to GCC and some other compilers). All source files must be compiled with this definition if any are or undefined behavior may result. There could be a significant performance cost to initializing all arrays this way and it can mask initialization errors so this is recommended primarily for transitional use while migrating and debugging code that depended on a Fortran compiler (such as g77) that did (non-standard) zero-initialization of arrays. Explicit DimensioningAlthough Fortran 77 does not provide array resizing real Arrays can be dynamically (re)sized via the allocate and dimension member functions:
The basic allocate and dimension calls discard the data that was in the real Array, removes any initializer associated with it, and does not initialize the redimensioned array (unless the code was compiled with OBJEXXFCL_ARRAY_INIT defined). The allocate method is provided as an analog to Fortran's ALLOCATE function and a non-member allocate function is also provided. A variant of dimension is provided with an extra initializer argument. The redimension call preserves the data in the real Array that overlaps with the new dimensions. The elements in any added portions are not initialized unless the redimension variant with a fill value is used:
The redimension calls removes any initializer associated with the real Array (a fill value is not treated as a new initializer). A new initializer can be assigned to the Array by an explicit initializer call (see above). Real Arrays can also be dimensioned and redimensioned to match the dimensions of another array:
Real Arrays do not need to be explicitly deallocated before reallocating them with allocate, dimension, or redimension calls. If the reallocation size happens to be the same as the current size the allocation methods will take advantage of that so not calling deallocate will save the cost of a heap allocation. Warning! Resizing a real Array invalidates any argument Arrays attached to it. IteratorsArrays provide an iterator interface with begin, end, rbegin, and rend methods. This allows the Arrays to be used with many C++ Standard Library functions and C++11 range-based for loops. For multidimensional Arrays it is important to understand that iterators provide a memory-order traversal, which is row-major order for this variant of the ObjexxFCL. Array1D Vector-Like InterfaceArray1D has an additional std::vector-like personality for more natural use as a general purpose C++ container with the performance capabilities of std::vector. The methods in this API are shown below. Array1D has the same overloads of these that std::vector provides.
Other than front and back, these methods change the size of the Array1D. As with std::vector they use an excess capacity strategy to reduce the number of expensive heap allocations. The presence of excess capacity is treated as an indicator that an Array1D is in a vector-like mode where excess capacity should be exploited to tradeoff greater memory use for performance. If an Array1D has excess capacity, allocate and dimension will use that capacity when possible to avoid a heap allocation. Argument ArraysArgument Arrays behave in most ways like a real Array but they attach to the data of another Array and do not allocate or own their array data. Argument Arrays can attach to all or part of Arrays of any rank and dimensions. Typically, they may provide a smaller, lower rank "view" of a slice of a larger array or simply a lower rank "view" of a whole array. This provides the ability to mimic Fortran array passing “tricks” and the memory sharing of a Fortran EQUIVALENCE between two arrays or array sections. Argument Arrays have names of the form ArrayNA<type> with shorthand names of the form ArrayNA_type where N is the rank of the array and type is the value type the array hold. ConstructionArgument Arrays can be constructed in a number of ways. Default construction creates an undimensioned argument Array that is not attached to another Array:
Default-constructed argument Arrays can be later attached and dimensioned with attach and dimension calls as described below. Whole array construction attaches the argument Array to the whole source Array:
You can also construct a whole-array argument Array and specify its dimensions with multi-argument constructors:
Argument Arrays can be constructed from elements of another Array to create a view of a contiguous portion of the array:
The D array is passed an element so it is constructed without knowledge of the extent of valid memory it can access. The index ranges specified may be within that valid extent or not. This mechanism allows support of Fortran array passing usage but exposes code to array bounds violations that can't be detected by the argument Array classes. The E array is passed an element using the a function, which creates an ArrayTail object that knows its size, which allows bounds error detection in debug builds but at some cost in construction time. When single Array argument constructors are used, as when creating an argument Array via pass-by-value, default argument Array dimensions are used:
Attach/DetachArgument Arrays can be dynamically attached and detached from source arrays:
A call to detach detaches an argument Array from its source array. It isn't necessary to explicitly detach before attaching to a new source array: that happens automatically. The dimension calls (see below) are chained after the attach calls to set the dimensions. The active member function will tell you whether an argument Array is currently attached to a source array. Explicit DimensioningArgument Arrays can be dynamically (re)sized via the dimension member function using a syntax similar to their constructors:
Dimensioning argument Arrays only changes the perceived size of the arrays' view and doesn't cause reallocation of a data array, so no initializing variants of dimension are provided. From the argument Array's perspective the elements are reflowed in row-major order to fill the new dimensions, unlike a data-preserving redimension call on a real Array, which keeps elements in their original index positions as the dimensions change. Attempting to dimension an argument Array to a size that exceeds the actual size of the associated data array, if known, will trigger an assertion failure in debug builds. Argument Arrays can also be dimensioned to match the dimensions of another array:
An assumed-size (upper-unbounded) dimension can be specified for the last dimension by using an underscore, _ (a predefined instance of Omit):
The use of _ for the last dimension is analogous to Fortran's assumed-size array dimensioning. If the actual array size is known then the last dimension will be set appropriately so that bounds errors can be caught by assertion failures in debug builds, but if an array element is passed by the fast method the last dimension will have an unbounded upper extent and the array size will be unbounded. The dim function can be used to dimension an argument Array after it is constructed, such as one created via a pass-by-value argument:
The dim function is like dimension but can be used to set up the argument Array dimensions even if it is const, such as in the example above: this is needed since we can't set the desired dimensions in the argument declaration. When an argument Array is declared as a pass-by-value argument of a function the actual argument passed becomes the constructor argument to the argument Array. Argument Arrays must be passed by value to create a proxy of the passed array, but they can also be passed by reference when proxy creation isn't needed (no change to rank or dimensions is required). Although there is no copying of the array data when argument Arrays are constructed there is a small construction cost. Const-CorrectnessArgument Arrays have proxy semantics and share the const-correctness semantics of pointers. The constness of the proxy and the data it is a proxy for are distinct and both are needed to get full constness:
One approach sometimes used to address this issue is to provide always-const versions of the proxy classes. This has the benefit of compile-time const correctness checks but forces a lot of code changes and limits the legal attach operations. Argument Arrays are typically created via pass-by-value function arguments. If the functions do not need to alter the array values then qualify them with const in the function declaration, assuring const-correctness is preserved. Alternatively, a const reference to the argument Arrays can be used for subscripting operations. Warning!: Resizing a real Array invalidates any argument Arrays that are attached to its data. VectorizationVectorization allows modern CPUs to process multiple loop passes in a single operation on SIMD hardware. There are restrictions on the types of loops that can be vectorized but the performance benefits of vectorization are large enough that sometimes it is worthwhile rewriting loops to make them amenable to vectorization. Modern C++ compilers have auto-vectorization support that can vectorize a subset of amenable loops automatically, without any developer intervention. Each compiler has a different method of enabling auto-vectorization but many enable it at the higher optimization levels. Because vectorization reorders operations the "fast floating point" mode (/fp:fast on Intel C++ and -Ofast or -ffast-math on GCC) must be enabled to allow floating point loops to vectorize. Good performance can normally be obtained just by enabling auto-vectorization across your application. In performance-critical sections it may be possible to further improve performance by explicit vectorization hints and/or restructing the loops to make them more amenable to the auto-vectorizers. This is a large and complex topic that is covered in depth elsewhere. Alignment of array elements, which is beneficial for vectorization performance, is discussed next. AlignmentAligned array elements allow more efficient vectorization by eliminating the up-front "peel" loop needed to deal with the unaligned elements. While unaligned access has a smaller performance cost on modern CPUs usign aligned array data is still faster. The ObjexxFCL real arrays, ArrayND, have optional element alignment support to enable more efficient vectorization. To enable aligned array element data so that the first array element address is aligned properly for the target build hardware define the OBJEXXFCL_ALIGN preprocessor symbol in the compilation process. This will align the Array (and CArrayA) first elements to be aligned properly for the target hardware. For SSE targets this gives 16 byte alignment, for AVX and AVX2 this gives 32 byte alignment, and for AVX-512 and MIC (Intel Phi) targets this gives 64 byte alignment. The alignment can be overridden to specify a larger (power of 2) alignment by giving a value to OBJEXXFCL_ALIGN. For example, to get 64 byte alignment on AVX2 hardware, perhaps to match the cache line size, you can add a compile flag that typically looks like -DOBJEXXFCL_ALIGN=64. For 2+ dimension arrays aligning the first element is not sufficient for optimal performance: ideally you want the first element of the innermost loop "row" aligned. Considering 2D arrays, there are a couple of ways to achieve this:
For the compiler to exploit array alignment additional "hints" must be added to your code. One method is to use "assume aligned" directives: these differ across platforms but the vectorize.hh header has a portable macro ASSUME_ALIGNED_OBJEXXFCL(p) where p is a pointer to the first array element (this macro knows the ObjexxFCL alignment so it isn't passed as an argument). The "assume aligned" approach has the downside that the address of Array elements can't be passed so the loop must be rewritten with raw pointers. A simpler approach is avaiable for Intel C++ when all array accesses are aligned by placing a #pragma vector aligned line before the loop, so placing a block like this before the for loop should suffice: #if defined(__INTEL_COMPILER) && defined(OBJEXXFCL_ALIGN) #pragma vector aligned #endif Slice ArraysSlice Arrays are array proxies that present a slice of another array as an array of the same or lesser rank. A slice Array is normally created by an indexing operation on an array where some or all of the indexes are index slices rather than scalar indexes. An index slice has a start index, an end index, and a stride, or step, value. Index slices are denoted with a {start,end,stride} syntax:
Details about slice arrays:
Array PassingReal Arrays can be used as function arguments that are declared as passed by value (expensive), reference, or const reference. Within the functions those Arrays have the same rank and dimensions as the passed Array. To pass Arrays of different rank or dimensions or size or to pass Array elements as the starting point of an Array section or slice the function argument must be an argument Array declared as pass-by-value. These arrays are constructed at the time of the function call but the underlying data is not copied: since they are proxies for the passed arrays data so the construction cost is modest. Arrays and array elements of any rank and size Array can be passed to an argument Array. Although the array data is not copied, the construction of argument Arrays at each function call can add significant overhead for heavily called functions compared with passing Arrays by reference. If a function will always be passed arrays of the rank and dimensions used by the function it is more efficient for the array arguments to be passed by reference. To support passing all types of Array of a given rank and value type to a function the arguments should be declared references to the base Array class of that rank and value type:
Base Arrays support all Array operations except those with semantics that are specific to the concrete Array type, such as dimension or attach. Array Element PassingThere are two ways to pass an array element to an argument array, faster or safer:
The faster method is transparent in the call statement but does not provide the function information about the actual size of the underlying array. The safer method uses the a member function to provide a proxy for the array tail section starting at the specified element that allows the function to check for array bounds errors in debug builds. Array SizeArray sizes are limited to 2N-2 where N is the bit size of std::size_t, so very large arrays are supported on 64-bit platforms where std::size_t is 64 bits. The index range for each dimension is indexed by int, which is still 32 bits on 64-bit platforms, so the size along each dimension is more limited than the total array size on 64-bit platforms. Attempting to create an array with a dimension or total size exceeding these limits is caught by assertion-enabled debug builds. The operating system on a given platform may further limit the allocatable size. The size of an Array can be obtained via the size member function. For real Arrays this is the actual array data size and for argument Arrays this is the size of the active array part that it is using, which may be unknown/unbounded. The size_bounded and size_unbounded member functions can be used to test whether the active array has unbounded/unknown extent. The size of the actual array data can be obtained via the array_size member function, and the array_size_bounded and array_size_unbounded member functions can be used to test whether the actual array data has unknown/unbounded extent. Array Size ReportsThe array name or location of creation isn't known at the point of reporting but the report includes the value type, element count, and index ranges to aid in the identification of the associated array. The value type held by the Array is the typeid's name() string for that type, which varies by compiler. The codes used by GCC are:
The size is computed as the number of elements times the sizeof the Array's value type, so Arrays holding value types that have associated heap memory do not show the full memory they use. Array Activity StateThe active member function can be called to determine whether a real Array is allocated or whether an argument Array is attached to another Array. Generator FunctionsOperator functions that generate Arrays include the unary minus function and sums and differences of Arrays of the same rank and dimensions and of Arrays and scalar values:
These operators accept all types of Arrays but always produce real Arrays. Special FunctionsArray1D arrays have the length and length_squared member functions to compute their L2 norm length and squared length and the normalize function to normalize the array to unit length. The dot_product function computes the dot (inner) product of two Array1Ds. The cross_product function computes the cross product of two Array1Ds of size 3. Array2D arrays have the boolean square function to test whether the array is square, the to_identity function to make a square array into the identity matrix and the to_diag function to make a square array into a diagonal matrix with a uniform value. The transpose function transposes a square array and the transposed function returns a transposed copy of the array. The identity and diag static functions are named constructors to create identity and diagonal matrices, respectively. The swap functions allow two real Arrays of the same rank to efficiently swap their contents. There is a single-argument swap member function and a two-argument swap non-member function. Use A.swap( B ) or swap( A, B ) (using std::swap( A, B ) wil be slower on compilers that don't support the provided overloaded function templates). Array DebuggingFor Fortran-like performance, the Array classes don't check for array bounds errors in release builds (when NDEBUG is defined). It is therefore important to test assertion-enabled debug builds of code using the Array classes. To get all argument Arrays bounds checked it would be necessary to use the safer array element passing method described above, but at some cost to the performance of release builds. Array OutputStream output operators are provided for each Array. These are simple output of elements in memory order without an attempt to organize by rows or slices, so custom output functions will be needed for application-specific formatting. Array HeadersHeaders are provided for each Array rank and type:
Headers for each rank and type of Array that are used more than "in name" must be included. To use all array types of a given rank N include ArrayN.all.hh to. Including only the Array headers needed will reduce compilation time. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | | | | | | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © 2000-2017 Objexx Engineering, Inc. All Rights Reserved. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |