Aligned Arrays Implementation
This is an implementation of page size aligned arrays. It is interchangeable with the standard dynamic array type and can implicitly convert between the two. TheTAlignedArray<T>
type exposes aproximately the same functionality as dynamic arrays, but with page sized and aligned pointers. It uses the new management operators to allocate and deallocate memory.Please note, that when you assign one aligned array to another aligned array, new page aligned memory is allocated and memory is copied. If you want to prevent this from happening you should either pass the arrays as constref or by reference. You can use the type
PAlignedArrayType
as a way of passing references safely without falling back to using var
arguments.Here is the basic usage of
TAlignedArray<T>
:
procedure Test;
var
A, B: TAlignedArray<TPoint>;
C: array of TPoint;
P: TPoint;
begin
A.Push(Point(12, 15));
A.Push(Point(8, 4));
B := A;
WriteLn('Memory address of first A item ', IntPtr(A[0]));
WriteLn('First item of A is page aligned: ', IntPtr(A[0]) mod PageSize = 0);
WriteLn('Memory address of first B item ', IntPtr(B[0]));
WriteLn('First item of B is page aligned: ', IntPtr(B[0]) mod PageSize = 0);
WriteLn('Points in A:');
for P in A do
WriteLn('X: ', P.X, ', Y: ', P.Y);
WriteLn('Points in B:');
for P in B do
WriteLn('X: ', P.X, ', Y: ', P.Y);
C := A;
WriteLn('Copied to standard dynamic array C');
WriteLn('Points in C:');
for P in C do
WriteLn('X: ', P.X, ', Y: ', P.Y);
end;
Which results in this:
Memory address of first A item 14606336 First item of A is page aligned: TRUE Memory address of first B item 14614528 First item of B is page aligned: TRUE Points in A: X: 12, Y: 15 X: 8, Y: 4 Points in B: X: 12, Y: 15 X: 8, Y: 4 Copied to standard dynamic array C Points in C: X: 12, Y: 15 X: 8, Y: 4If you want to increase the speed when working with aligned arrays, you can set the length directly on an aligned array, request the first item, and safely access subsequent items using the
Inc
procedure.And here is the implementation of
TAlignedArray<T>
:
unit AlignArrays;
{$mode delphi}
interface
type
TMemoryEnumerator<T> = class(TInterfacedObject, IEnumerator<T>)
private
type PItem = ^T;
private
FMemory: Pointer;
FPosition: Integer;
FCount: Integer;
public
constructor Create(Memory: Pointer; Count: Integer);
function GetCurrent: T;
function MoveNext: Boolean;
procedure Reset;
property Current: T read GetCurrent;
end;
TAlignedArray<T> = record
public
type
TArray = array of T;
TAlignedArrayType = TAlignedArray<T>;
PAlignedArrayType = ^TAlignedArrayType;
TReference = ^T;
TValue = T;
IEnumeratorType = IEnumerator<T>;
function GetEnumerator: IEnumeratorType;
private
FPage: Pointer;
FCount: Integer;
FLength: Integer;
procedure SetLength(Value: Integer);
function GetReference(Index: Integer): TReference;
function GetItem(Index: Integer): TValue;
procedure SetItem(Index: Integer; const Value: TValue);
public
class operator Initialize(var A: TAlignedArrayType);
class operator Finalize(var A: TAlignedArrayType);
class operator Copy(constref Source: TAlignedArrayType; var Dest: TAlignedArrayType);
class operator Implicit(const Value: TAlignedArrayType): TArray;
class operator Implicit(const Value: TArray): TAlignedArrayType;
class operator Implicit(const Value: array of T): TAlignedArrayType;
procedure Clear;
procedure Push(const Item: TValue);
function Pop: TValue;
property Length: Integer read FLength write SetLength;
property Reference[Index: Integer]: TReference read GetReference; default;
property Item[Index: Integer]: TValue read GetItem write SetItem;
end;
const
PageSize = 1024 * 4;
function PagesAlloc(Count: Integer): Pointer;
procedure PagesFree(var Page: Pointer);
implementation
{$ifdef unix}
const
libc = 'libc';
function posix_memalign(out ptr: Pointer; alignment: IntPtr; size: IntPtr): Integer; cdecl; external libc;
procedure free(ptr: Pointer); cdecl; external libc;
{$endif}
{$ifdef windows}
const
msvcrt = 'msvcrt.dll';
function _aligned_malloc(size: IntPtr; alignment: IntPtr): IntPtr; cdecl; external msvcrt;
function _aligned_free(memblock: IntPtr): IntPtr; cdecl; external msvcrt;
{$endif}
function PagesAlloc(Count: Integer): Pointer;
{$ifdef unix}
var
I: Integer;
begin
if Count > 0 then
begin
I := posix_memalign(Result, PageSize, Count * PageSize);
if I <> 0 then
Result := nil;
end
else
Result := nil;
end;
{$endif}
{$ifdef windows}
begin
if Count > 0 then
Result := _aligned_malloc(Count * PageSize, PageSize)
else
Result := nil;
end;
{$endif}
procedure PagesFree(var Page: Pointer);
var
P: Pointer;
begin
P := Page;
Page := nil;
if P <> nil then
{$ifdef unix}
free(P);
{$endif}
{$ifdef windows}
_aligned_free(P);
{$endif}
end;
{ TMemoryEnumerator<T> }
constructor TMemoryEnumerator<T>.Create(Memory: Pointer; Count: Integer);
begin
inherited Create;
FMemory := Memory;
FPosition := -1;
FCount := Count;
end;
function TMemoryEnumerator<T>.GetCurrent: T;
var
Item: PItem;
begin
Item := FMemory;
Inc(Item, FPosition);
Result := Item^;
end;
function TMemoryEnumerator<T>.MoveNext: Boolean;
begin
Inc(FPosition);
Result := FPosition < FCount;
end;
procedure TMemoryEnumerator<T>.Reset;
begin
FPosition := -1;
end;
{ TAlignedArray<T> }
function TAlignedArray<T>.GetEnumerator: IEnumeratorType;
begin
Result := TMemoryEnumerator<T>.Create(FPage, FLength);
end;
class operator TAlignedArray<T>.Initialize(var A: TAlignedArrayType);
begin
A.FPage := nil;
A.FCount := 0;
A.FLength := 0;
end;
class operator TAlignedArray<T>.Finalize(var A: TAlignedArrayType);
begin
A.Clear;
end;
class operator TAlignedArray<T>.Copy(constref Source: TAlignedArrayType; var Dest: TAlignedArrayType);
var
S, D: TReference;
I: Integer;
begin
if @Source = @Dest then
Exit;
Dest.Length := Source.Length;
S := Source.FPage;
D := Dest.FPage;
for I := 0 to Source.Length - 1 do
begin
D^ := S^;
Inc(S);
Inc(D);
end;
end;
class operator TAlignedArray<T>.Implicit(const Value: TAlignedArrayType): TArray;
var
I: Integer;
begin
System.SetLength(Result, Value.Length);
for I := 0 to Value.Length - 1 do
Result[I] := Value.Item[I];
end;
class operator TAlignedArray<T>.Implicit(const Value: TArray): TAlignedArrayType;
var
R: TReference;
I: Integer;
begin
Result.Length := System.Length(Value);
R := Result.FPage;
for I := 0 to Result.Length - 1 do
begin
R^ := Value[I];
Inc(R);
end;
end;
class operator TAlignedArray<T>.Implicit(const Value: array of T): TAlignedArrayType;
var
R: TReference;
I: Integer;
begin
Result.Length := System.Length(Value);
R := Result.FPage;
for I := 0 to Result.Length - 1 do
begin
R^ := Value[I];
Inc(R);
end;
end;
procedure TAlignedArray<T>.Clear;
begin
Length := 0;
end;
procedure TAlignedArray<T>.Push(const Item: TValue);
var
I: Integer;
begin
I := FLength;
SetLength(FLength + 1);
GetReference(I)^ := Item;
end;
function TAlignedArray<T>.Pop: TValue;
var
I: Integer;
begin
I := FLength - 1;
if I > -1 then
begin
Result := GetReference(I)^;
GetReference(I)^ := Default(TValue);
SetLength(I);
end;
end;
procedure TAlignedArray<T>.SetLength(Value: Integer);
var
Buffer: TReference;
A, B: TReference;
I: Integer;
begin
if Value < 1 then
begin
if FLength = 0 then
Exit;
A := FPage;
for I := 0 to FLength - 1 do
begin
A^ := Default(TValue);
Inc(A);
end;
FCount := 0;
FLength := 0;
PagesFree(FPage);
end
else if Value < FLength then
begin
A := FPage;
Inc(A, FLength - 1);
for I := FLength -1 downto Value do
begin
A^ := Default(TValue);
Dec(A);
end;
FLength := Value;
end
else if Value > FLength then
begin
I := Value div SizeOf(TValue) + 1;
if I > FCount then
begin
Buffer := PagesAlloc(I);
FillChar(Buffer^, PageSize * I, 0);
B := Buffer;
for I := 0 to Value - 1 do
begin
B^ := Default(TValue);
Inc(B);
end;
A := FPage;
B := Buffer;
for I := 0 to FLength - 1 do
begin
B^ := A^;
A^ := Default(TValue);
Inc(A);
Inc(B);
end;
PagesFree(FPage);
FPage := Buffer;
FCount := Value div SizeOf(TValue) + 1;
FLength := Value;
end
else
begin
A := FPage;
Inc(A, FLength);
for I := FLength to Value - 1 do
begin
A^ := Default(TValue);
Inc(A);
end;
FLength := Value;
end;
end;
end;
function TAlignedArray<T>.GetReference(Index: Integer): TReference;
begin
if Index < 0 then
Exit(nil);
if Index > FLength - 1 then
Exit(nil);
Result := TReference(FPage);
if Index > 0 then
Inc(Result, Index);
end;
function TAlignedArray<T>.GetItem(Index: Integer): TValue;
begin
Result := GetReference(Index)^;
end;
procedure TAlignedArray<T>.SetItem(Index: Integer; const Value: TValue);
begin
GetReference(Index)^ := Value;
end;
end.