Delphi Array Alignment set to 4, 8, or 16 byte boundaries?
Question
I would like to use the FFTW C library from Delphi 2009 and according to this documentation;
http://www.fftw.org/install/fftw_usage_from_delphi.txt
to increase the performance inside the FFTW library (such that it can use SIMD extensions) arrays passed in of either Single (float) or Double (double) need to be aligned either at 4 or 8 byte boundaries. I found documentation talking about alignment of record structures, but nothing specific about arrays. Is there a way to do this in Delphi 2009.
So the code (copied from the above documentation) would look like this;
var
in, out : Array of Single; // Array aligned at 4 byte boundary
plan : Pointer;
{$APPTYPE CONSOLE}
begin
...
SetLength(in, N);
SetLength(out, N);
plan := _fftwf_plan_dft_1d(dataLength, @in[0], @out[0],
FFTW_FORWARD, FFTW_ESTIMATE);
Also in the above documentation they talk about 8 and 16 byte boundaries but it looks to me it should be 4 and 8 byte boundaries, if any could clear that up to that would be great.
Thanks, Bruce
Solution
Delphi provides no way to control the alignment of any memory it allocates. You're left to either rely on the documented behavior for the memory manager currently installed, or allocate memory with some slack space and then align it yourself, as Mghie demonstrates.
If you're concerned that Delphi's memory manager is not providing the desired alignment for dynamic arrays, then you can go ahead and use the memory functions provided by the DLL. The note you cite mentions _fftwf_malloc
and _fftwf_free
, but then it gives some kind of warning that memory allocated from _fftwf_malloc
"may not be accessed directly from Delphi." That can't be what the authors meant to say, though, because that's not how memory works in Windows. The authors probably meant to say that memory allocated by _fftwf_malloc
cannot be freed by Delphi's FreeMem
, and memory allocated by Delphi's GetMem
cannot be freed by _fftwf_free
. That's nothing special, though; you always need to keep your memory-management functions paired together.
If you use _fftwf_malloc
to get your array, then you can access it through an ordinary pointer type. For example:
var
dataIn, dataOut: PDouble;
begin
dataIn := _fftwf_malloc(...);
dataOut := _fftwf_malloc(...);
_fftwf_plan_dft_1d(dataLength, dataIn, dataOut,
FFTW_FORWARD, FFTW_ESTIMATE);
As of Delphi 2009, you can even use array syntax on those pointers:
dataIn[0] := 3.5;
dataIn[2] := 7.3;
In order to enable that, use the {$POINTERMATH ON}
compiler directive; it's not enabled by default except for the character-pointer types.
The disadvantage to manually allocating arrays like this is that you lose range checking. If you index beyond the end of an array, you won't get an easy-to-recognize ERangeError
exception anymore. You'll get corrupted memory, access violations, or mysteriously crashing programs instead.
OTHER TIPS
Note that you can create data structures with any custom alignment you might need. For example to align your FFT data on 128 byte boundaries:
procedure TForm1.Button1Click(Sender: TObject);
type
TFFTData = array[0..63535] of double;
PFFTData = ^TFFTData;
var
Buffer: pointer;
FFTDataPtr: PFFTData;
i: integer;
const
Alignment = 128; // needs to be power of 2
begin
GetMem(Buffer, SizeOf(TFFTData) + Alignment);
try
FFTDataPtr := PFFTData((LongWord(Buffer) + Alignment - 1)
and not (Alignment - 1));
// use data...
for i := Low(TFFTData) to High(TFFTData) do
FFTDataPtr[i] := i * pi;
finally
FreeMem(Buffer);
end;
end;
Edit:
Regarding the comment about twice the memory being allocated: The stack variable FFTData is of type PFFTData, not of TFFTData, so it's a pointer. It's not that obvious because of the syntax enhancement allowing to omit the ^ for dereferencing the pointer. The memory is allocated with GetMem(), and to work with the proper type instead of the untyped memory block the typecast is employed. I should probably have called it FFTDataPtr.
Heap blocks are iirc always aligned to 16-byte bounderies by FastMM (the old D7 memmanager aligned to 8). I don't know about sharemem, since I don't use it.
And dynamic arrays are heap based structures. OTOH dyn arrays could maybe become unaligned (from 16 to 8) because there is a length and ref count prefixed. Easiest is to simply print
ptruint(@in[0]) in hex and see if the end is 0 or 8. (*)
Note that there are fftw headers in FPC. ( packages/fftw), afaik it was recently fixed for 64-bit even.
I'm not aware of Stack alignment directives in Delphi. Maybe they are automatically "naturally" aligned though.
(*) ptruint is FPC speak for an unsigned integer type that is sizeof(pointer) large. cardinal on 32-bit, qword on 64-bit.
This is another possible variant of mghie's solution:
procedure TForm1.Button1Click(Sender: TObject);
type
TFFTData = array [0..0] of Double;
PFFTData = ^TFFTData;
var
AllocatedBuffer: Pointer;
AlignedArray: PFFTData;
i: Integer;
const
cFFTDataSize=63536;
begin
GetMem(AllocatedBuffer, cFFTDataSize*SizeOf(Double) + 16); // e.g 16 Bytes boudaries alignement
try
AlignedArray := PFFTData((Integer(AllocatedBuffer) and $FFFFFFF0) + 16);
// use data...
for i := 0 to cFFTDataSize-1 do
AlignedArray[i] := i * Pi;
finally
FreeMem(AllocatedBuffer);
end;
end;
I've refactored the piece of code to make it more meaningfull and make use of a similar manual alignement fix up technique.