Delphi and Free Pascal bencmark: array vs TList vs dynamic array
Let's perform a simple benchmark testing the random and sequential access to arrays and lists. I would like to compare:
- static array with predefined size (allocated on stack)
- TListcontainer class (allocated on heap)
- dynamic array (allocated on heap)
- generic Delphi TListor Free PascalTFPGList(allocated on heap)
I use an array/list of 10M elements of integer and varaint data type. You can change the type definition in "type" section if need to test other cases.
program Bench1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
  SysUtils,
  Classes,
  DateUtils,
  {$IFDEF FPC}FGL{$ELSE}System.Generics.Collections{$ENDIF},
  Math;
const
  MaxDimension = 10000000;
type
  TTestedValueType = integer;
  PTestedValueType = ^TTestedValueType;
  TTestGenericList = {$IFDEF FPC}specialize TFPGList<PTestedValueType>{$ELSE}TList<PTestedValueType>{$ENDIF};
var
  D1, D2: TDateTime;
  RndAddr: array [0..MaxDimension - 1] of integer;
  TestArray: array [0..MaxDimension - 1] of TTestedValueType;
  TestList: TList;
  TestGenericList: TTestGenericList;
  TestDynArray: array of TTestedValueType;
  p: PTestedValueType;
  i: integer;
  Sum: double;
begin
  Randomize;
  writeln('Prepare random access array');
  for i := 0 to MaxDimension - 1 do
    RndAddr[i] := Math.RandomRange(0, MaxDimension);
  // Static array
  for i := 0 to MaxDimension - 1 do
    TestArray[i] := 0;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    TestArray[RndAddr[i]] := RndAddr[i];
  D2 := Now;
  writeln('Static array. Random write access. Elapsed, sec: ', DateUtils.SecondSpan(D2, D1):5:2);
  Sum := 0;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    Sum := Sum + TestArray[i];
  D2 := Now;
  writeln('Static array. Sequential read access. Elapsed, sec: ',
    DateUtils.SecondSpan(D2, D1):5:2,
    '. Sum: ', Sum);
  // TList
  writeln('Prepare list');
  TestList := TList.Create;
  TestList.Capacity := MaxDimension;
  for i := 0 to MaxDimension - 1 do
  begin
    new(p);
    p^ := 0;
    TestList.Add(p);
  end;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    PTestedValueType(TestList[RndAddr[i]])^ := RndAddr[i];
  D2 := Now;
  writeln('TList. Random write access. Elapsed, sec: ', DateUtils.SecondSpan(D2, D1):5:2);
  Sum := 0;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    Sum := Sum + PTestedValueType(TestList[i])^;
  D2 := Now;
  writeln('TList. Sequential access. Elapsed, sec: ',
    DateUtils.SecondSpan(D2, D1):5:2,
    '. Sum: ', Sum);
  for i := 0 to MaxDimension - 1 do
    dispose(PTestedValueType(TestList[i]));
  TestList.Free;
  // Dynamic array
  writeln('Prepare dynamic array');
  SetLength(TestDynArray, MaxDimension);
  for i := 0 to MaxDimension - 1 do
    TestDynArray[i] := 0;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    TestDynArray[RndAddr[i]] := RndAddr[i];
  D2 := Now;
  writeln('Dynamic array. Random write access. Elapsed, sec: ', DateUtils.SecondSpan(D2, D1):5:2);
  D1 := Now;
  Sum := 0;
  for i := 0 to MaxDimension - 1 do
    Sum := Sum + TestDynArray[i];
  D2 := Now;
  writeln('Dynamic array. Sequential access. Elapsed, sec: ',
    DateUtils.SecondSpan(D2, D1):5:2,
    '. Sum: ', Sum);
  SetLength(TestDynArray, 0);
  // Generic list
  writeln('Prepare generic list');
  TestGenericList := TTestGenericList.Create;
  TestGenericList.Capacity := MaxDimension;
  for i := 0 to MaxDimension - 1 do
  begin
    new(p);
    p^ := 0;
    TestGenericList.Add(p);
  end;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    PTestedValueType(TestGenericList[RndAddr[i]])^ := RndAddr[i];
  D2 := Now;
  writeln('TTestGenericList. Random write access. Elapsed, sec: ', DateUtils.SecondSpan(D2, D1):5:2);
  Sum := 0;
  D1 := Now;
  for i := 0 to MaxDimension - 1 do
    Sum := Sum + PTestedValueType(TestGenericList[i])^;
  D2 := Now;
  writeln('TTestGenericList. Sequential read access. Elapsed, sec: ',
    DateUtils.SecondSpan(D2, D1):5:2,
    '. Sum: ', Sum);
  for i := 0 to MaxDimension - 1 do
    dispose(PTestedValueType(TestGenericList[i]));
  TestGenericList.Free;
end.
Results
For integer data type both static and dynamic arrays are faster than the lists. Now change value type to variant.
type
    TTestedValueType = variant;
After restarting the test, you can observe the same sample.
| Integer | Variant | |||
|---|---|---|---|---|
| Random write access, sec | Sequential read access, sec | Random write access, sec | Sequential read access, sec | |
| Free Pascal | ||||
| Static array | 0.20 | 0.04 | 0.77 | 1.02 | 
| TList | 0.96 | 0.08 | 1.74 | 1.06 | 
| Dynamic array | 0.20 | 0.04 | 0.74 | 1.06 | 
| TTestGenericList | 1.37 | 0.10 | 1.87 | 1.23 | 
| Delphi | ||||
| Static array | 0.29 | 0.04 | 0.63 | 0.52 | 
| TList | 2.29 | 0.05 | 3.59 | 0.54 | 
| Dynamic array | 0.29 | 0.04 | 0.69 | 0.53 | 
| TTestGenericList | 2.31 | 0.05 | 3.48 | 0.57 | 
Conclusions
For different value types like integer or variant the static or dynamic array are the fastest solutions in Delphi and FPC.
Remember, that "fastest" doesn't mean "better".
Test environment
- HP ProBook 6550b, Intel(R) Core(TM) i5 CPU M 520, RAM 8 GB
- Free Pascal case:- Ubuntu 14.04 64 bits Linux 3.16.0-60-generic
- Free Pascal Compiler version 3.0.0 [2015/12/05] for x86_64
- O1 optimization level
 
- Delphi case:- Windows 10 64 bits
- Delphi 10.2 Tokyo
- Optimization is on