Storage management
FEAT2 has an in-build storage management that identifies memory blocks via handles. Such handles are usually stored in structures instead of pointers to the actual memory. There are a couple of advantages in using this technique:
- A handle is always of type integer. Therefore, structures containing handles to identify memory block simplify, the actual structure of the memory does not have to be declared.
- A handle can be "shared" between different structures (just as a pointer as well). However, memory reallocation is more transparent. A reallocation does not change the handle, only the address that is associated to that handle.
- The total memory of the program can be measured.
- If memory allocation fails, the program can (theoretically) savely be aborted.
- Memory can be accessed on external devices as well (e.g., on graphics cards) where no pointer exists.
- Memory allocation can be tracked, memory holes can be found more easily.
The storage management is handled via the module storage.f90. The module has to be initialised at the beginning of the program via storage_init. At the end, storage_done has to be called which releases all allocated memory.
Allocation/use/deallocation of local memory
Allocation/use and deallocation of memory is always done in a very structured manner.
- Allocation of memory is done via
storage_new. This returns a handle to the memory. - With the handle, a pointer to the memory can be obtained via
storage_getbase_XXXX(with XXXX depending on the type of the memory object). - If the memory is not needed anymore, it can be released via
storage_free.
Here an example. We allocate a 1D array of 10 integers, initialise by zero, print the array and release it.
integer :: ihandle
integer, dimension(:), pointer :: p_Idata
...
! Allocate
call storage_new ("mysubroutine", "myarray", 10, &
ST_INT, ihandle, ST_NEWBLOCK_ZERO)
! Get the memory block
call storage_getbase_int (ihandle, p_Idata)
! Print
write (*,*) p_Idata
! Release
call storage_free (ihandle)
A second example allocates a 2D double precision array with 2x20 entries, initialise by zero, print the array and release it.
integer :: ihandle
integer, dimension(:,:), pointer :: p_Ddata
...
! Allocate
call storage_new ("mysubroutine", "myarray", (/10,2/), &
ST_DOUBLE, ihandle, ST_NEWBLOCK_ZERO)
! Get the memory block
call storage_getbase_double2d (ihandle, p_Ddata)
! Print
write (*,*) p_Ddata
! Release
call storage_free (ihandle)
Data types
The storage management supports a lot of different data/handle types, identified by a set of constants:
| Constant | Data type |
|---|---|
| ST_SINGLE | Single precision array |
| ST_DOUBLE | Double precision array |
| ST_QUAD | Quadrupel precision array |
| ST_SINGLE_COMPLEX | Single precision complex array |
| ST_DOUBLE_COMPLEX | Double precision complex array |
| ST_QUAD_COMPLEX | Quadrupel precision complex array |
| ST_LOGICAL | Bool array |
| ST_CHAR | Character array |
| ST_INT | Standard integer array |
| ST_INT8 | 8-bit integer array |
| ST_INT16 | 16-bit integer array |
| ST_INT32 | 32-bit integer array |
| ST_INT64 | 64-bit integer array |
These constants can be specified in the storage_new command as type for the array.
For example, the following call allocates a double precision array of size isize and returns a handle ihandle to it.
call storage_new (..., ..., isize, ST_DOUBLE, ihandle, ...)
The additional constant ST_NOHANDLE is used to identify a non-associated handle. storage_new returns ST_NOHANDLE if the allocation fails.
Supported array formats
The storage management supports three types of data arrays: 1D, 2D and 3D arrays. For each type of data and dimension, there exist a separate storage_new routine for creation and a storage_getbase_XXXX routine to retrieve the pointer behind the handle.
The creation of the array is always done with storage_new, which has a different syntax depending on whether a 1D, 2D or 3D array is created:
| Dim | Creation of the array |
|---|---|
| 1D | call storage_new ( ..., isize, ST_DOUBLE, ihandle, ...) |
| 2D | call storage_new (..., (/ isize1, isize2 /), ST_DOUBLE, ihandle, ...) |
| 2D | call storage_new (..., (/ isize1, isize2, isize3 /), ST_DOUBLE, ihandle, ...) |
For retrieving the pointer, one uses the appropriate storage_getbase_XXXXyD routine:
| Dim | Type | Retrieving the pointer |
|---|---|---|
| 1D | integer | integer, dimension(:), pointer :: p_Idatacall storage_getbase_int (ihandle,p_Idata) |
| 2D | integer | integer, dimension(:,:), pointer :: p_Idatacall storage_getbase_int2D (ihandle,p_Idata) |
| 3D | integer | integer, dimension(:,:,:), pointer :: p_Idatacall storage_getbase_int3D (ihandle,p_Idata) |
| 1D | double | real(DP), dimension(:), pointer :: p_Ddatacall storage_getbase_double (ihandle,p_Ddata) |
| 2D | double | real(DP), dimension(:,:), pointer :: p_Ddatacall storage_getbase_double2D (ihandle,p_Ddata) |
| 3D | double | real(DP), dimension(:,:,:), pointer :: p_Ddatacall storage_getbase_double3D (ihandle,p_Ddata) |
| ... | ... | ... |
Destruction of an array is always done with storage_free.
