2026-05-21 12:53:13 +03:00
|
|
|
{
|
|
|
|
|
Blaise - An Object Pascal Compiler
|
|
|
|
|
Copyright (c) 2026 Graeme Geldenhuys
|
|
|
|
|
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
|
|
|
|
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
|
|
|
|
See LICENSE file in the project root for full license terms.
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:37:22 +03:00
|
|
|
program BenchLists;
|
2026-05-21 12:53:13 +03:00
|
|
|
|
2026-05-21 16:37:22 +03:00
|
|
|
{ Micro-benchmark — TStringList vs TList<String> and TObjectList vs
|
|
|
|
|
TList<TObject>.
|
2026-05-21 12:53:13 +03:00
|
|
|
|
|
|
|
|
For each container we measure:
|
|
|
|
|
1. Bulk insert — 1_000_000 Adds
|
|
|
|
|
2. Sequential read — Get(i) for every index
|
|
|
|
|
3. Random read — 1_000_000 indexed reads in a pseudo-random pattern
|
2026-05-21 16:37:22 +03:00
|
|
|
4. Linear search — 1000 lookups; TList<T> uses a hand-rolled scan
|
|
|
|
|
since it has no IndexOf method
|
|
|
|
|
5. for..in iterate — full enumeration (strings workload only)
|
2026-05-21 12:53:13 +03:00
|
|
|
|
|
|
|
|
All times are wall-clock milliseconds via TInstant.Subtract.
|
|
|
|
|
Run from the project root:
|
|
|
|
|
|
2026-05-21 16:37:22 +03:00
|
|
|
compiler/target/blaise --source tests/bench_lists.pas --output /tmp/bench_lists
|
|
|
|
|
/tmp/bench_lists
|
|
|
|
|
}
|
2026-05-21 12:53:13 +03:00
|
|
|
|
|
|
|
|
uses
|
|
|
|
|
Classes,
|
2026-05-21 16:37:22 +03:00
|
|
|
Contnrs,
|
2026-05-21 12:53:13 +03:00
|
|
|
Generics.Collections,
|
|
|
|
|
DateUtils,
|
|
|
|
|
SysUtils;
|
|
|
|
|
|
|
|
|
|
const
|
|
|
|
|
N_BIG = 1000000;
|
|
|
|
|
N_MID = 100000;
|
|
|
|
|
N_RANDOM = 1000000;
|
|
|
|
|
N_SEARCH = 1000;
|
|
|
|
|
|
2026-05-21 16:37:22 +03:00
|
|
|
type
|
|
|
|
|
TItem = class
|
|
|
|
|
FValue: Integer;
|
|
|
|
|
end;
|
|
|
|
|
|
2026-05-21 12:53:13 +03:00
|
|
|
var
|
|
|
|
|
GRand: Integer;
|
|
|
|
|
|
|
|
|
|
procedure SeedRand(S: Integer);
|
|
|
|
|
begin
|
|
|
|
|
GRand := S
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function NextRand: Integer;
|
|
|
|
|
begin
|
|
|
|
|
GRand := GRand * 1103515245 + 12345;
|
|
|
|
|
Result := (GRand shr 16) and $7FFFFFFF
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function ElapsedMs(Start: TInstant): Int64;
|
|
|
|
|
var
|
|
|
|
|
Now: TInstant;
|
|
|
|
|
D: TDuration;
|
|
|
|
|
begin
|
|
|
|
|
Now := InstantNow;
|
|
|
|
|
D := Now.Subtract(Start);
|
|
|
|
|
Result := D.TotalMilliseconds
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function PadRight(const S: string; Width: Integer): string;
|
|
|
|
|
var
|
|
|
|
|
N: Integer;
|
|
|
|
|
begin
|
|
|
|
|
Result := S;
|
|
|
|
|
N := Width - Length(S);
|
|
|
|
|
while N > 0 do
|
|
|
|
|
begin
|
|
|
|
|
Result := Result + ' ';
|
|
|
|
|
N := N - 1
|
|
|
|
|
end
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function PadLeft(const S: string; Width: Integer): string;
|
|
|
|
|
var
|
|
|
|
|
N: Integer;
|
|
|
|
|
begin
|
|
|
|
|
Result := S;
|
|
|
|
|
N := Width - Length(S);
|
|
|
|
|
while N > 0 do
|
|
|
|
|
begin
|
|
|
|
|
Result := ' ' + Result;
|
|
|
|
|
N := N - 1
|
|
|
|
|
end
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure Report(const ALabel: string; AMs: Int64);
|
|
|
|
|
begin
|
|
|
|
|
WriteLn(' ' + PadRight(ALabel, 50) + PadLeft(IntToStr(AMs), 6) + ' ms')
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure BenchTStringList;
|
|
|
|
|
var
|
|
|
|
|
SL: TStringList;
|
|
|
|
|
I: Integer;
|
|
|
|
|
Idx: Integer;
|
|
|
|
|
T0: TInstant;
|
|
|
|
|
S: string;
|
|
|
|
|
Sum: Int64;
|
|
|
|
|
Hits: Integer;
|
|
|
|
|
begin
|
|
|
|
|
WriteLn('TStringList');
|
|
|
|
|
|
|
|
|
|
{ 1. Bulk insert }
|
|
|
|
|
SL := TStringList.Create;
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
for I := 0 to N_BIG - 1 do
|
|
|
|
|
SL.Add('item_' + IntToStr(I));
|
|
|
|
|
Report('insert 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 2. Sequential read }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to SL.Count - 1 do
|
|
|
|
|
begin
|
|
|
|
|
S := SL.Get(I);
|
|
|
|
|
Sum := Sum + Length(S)
|
|
|
|
|
end;
|
|
|
|
|
Report('sequential Get(i) x Count (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 3. Random read }
|
|
|
|
|
SeedRand(42);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to N_RANDOM - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
S := SL.Get(Idx);
|
|
|
|
|
Sum := Sum + Length(S)
|
|
|
|
|
end;
|
|
|
|
|
Report('random Get(i) x 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 5. for..in iterate }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for S in SL do
|
|
|
|
|
Sum := Sum + Length(S);
|
|
|
|
|
Report('for..in over 1_000_000 (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
SL.Free;
|
|
|
|
|
|
|
|
|
|
{ 4. Linear search on a 100k list }
|
|
|
|
|
SL := TStringList.Create;
|
|
|
|
|
for I := 0 to N_MID - 1 do
|
|
|
|
|
SL.Add('item_' + IntToStr(I));
|
|
|
|
|
SeedRand(7);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Hits := 0;
|
|
|
|
|
for I := 0 to N_SEARCH - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_MID;
|
|
|
|
|
if SL.IndexOf('item_' + IntToStr(Idx)) >= 0 then
|
|
|
|
|
Hits := Hits + 1
|
|
|
|
|
end;
|
|
|
|
|
Report('IndexOf x 1000 (hits=' + IntToStr(Hits) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
SL.Free;
|
|
|
|
|
WriteLn
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure BenchGenericListString;
|
|
|
|
|
var
|
2026-05-21 16:37:22 +03:00
|
|
|
L: TList<String>;
|
|
|
|
|
I: Integer;
|
|
|
|
|
Idx: Integer;
|
|
|
|
|
J: Integer;
|
|
|
|
|
T0: TInstant;
|
|
|
|
|
S: string;
|
|
|
|
|
Sum: Int64;
|
|
|
|
|
Hits: Integer;
|
2026-05-21 12:53:13 +03:00
|
|
|
Found: Boolean;
|
|
|
|
|
begin
|
|
|
|
|
WriteLn('TList<String>');
|
|
|
|
|
|
|
|
|
|
{ 1. Bulk insert }
|
|
|
|
|
L := TList<String>.Create;
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
for I := 0 to N_BIG - 1 do
|
|
|
|
|
L.Add('item_' + IntToStr(I));
|
|
|
|
|
Report('insert 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 2. Sequential read }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to L.Count - 1 do
|
|
|
|
|
begin
|
|
|
|
|
S := L.Get(I);
|
|
|
|
|
Sum := Sum + Length(S)
|
|
|
|
|
end;
|
|
|
|
|
Report('sequential Get(i) x Count (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 3. Random read }
|
|
|
|
|
SeedRand(42);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to N_RANDOM - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
S := L.Get(Idx);
|
|
|
|
|
Sum := Sum + Length(S)
|
|
|
|
|
end;
|
|
|
|
|
Report('random Get(i) x 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 5. for..in iterate }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for S in L do
|
|
|
|
|
Sum := Sum + Length(S);
|
|
|
|
|
Report('for..in over 1_000_000 (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
L.Free;
|
|
|
|
|
|
|
|
|
|
{ 4. Linear search — TList<T> has no IndexOf, hand-rolled scan }
|
|
|
|
|
L := TList<String>.Create;
|
|
|
|
|
for I := 0 to N_MID - 1 do
|
|
|
|
|
L.Add('item_' + IntToStr(I));
|
|
|
|
|
SeedRand(7);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Hits := 0;
|
|
|
|
|
for I := 0 to N_SEARCH - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_MID;
|
|
|
|
|
S := 'item_' + IntToStr(Idx);
|
|
|
|
|
Found := False;
|
|
|
|
|
for J := 0 to L.Count - 1 do
|
|
|
|
|
if L.Get(J) = S then
|
|
|
|
|
begin
|
|
|
|
|
Found := True;
|
|
|
|
|
break
|
|
|
|
|
end;
|
|
|
|
|
if Found then
|
|
|
|
|
Hits := Hits + 1
|
|
|
|
|
end;
|
|
|
|
|
Report('hand-rolled scan x 1000 (hits=' + IntToStr(Hits) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
L.Free;
|
|
|
|
|
WriteLn
|
|
|
|
|
end;
|
|
|
|
|
|
2026-05-21 16:37:22 +03:00
|
|
|
procedure BenchTObjectList;
|
|
|
|
|
var
|
|
|
|
|
OL: TObjectList;
|
|
|
|
|
I: Integer;
|
|
|
|
|
Idx: Integer;
|
|
|
|
|
T0: TInstant;
|
|
|
|
|
Item: TItem;
|
|
|
|
|
Sum: Int64;
|
|
|
|
|
Hits: Integer;
|
|
|
|
|
Needle: TItem;
|
|
|
|
|
begin
|
|
|
|
|
WriteLn('TObjectList(OwnsObjects=True)');
|
|
|
|
|
|
|
|
|
|
{ 1. Bulk insert }
|
|
|
|
|
OL := TObjectList.Create(True);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
for I := 0 to N_BIG - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Item := TItem.Create;
|
|
|
|
|
Item.FValue := I;
|
|
|
|
|
OL.Add(Item)
|
|
|
|
|
end;
|
|
|
|
|
Report('insert 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 2. Sequential read }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to OL.Count - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Item := TItem(OL.Get(I));
|
|
|
|
|
Sum := Sum + Item.FValue
|
|
|
|
|
end;
|
|
|
|
|
Report('sequential Get(i) x Count (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 3. Random read }
|
|
|
|
|
SeedRand(42);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to N_RANDOM - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
Item := TItem(OL.Get(Idx));
|
|
|
|
|
Sum := Sum + Item.FValue
|
|
|
|
|
end;
|
|
|
|
|
Report('random Get(i) x 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 4. IndexOf — search for known objects (pointer-equality match) }
|
|
|
|
|
SeedRand(7);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Hits := 0;
|
|
|
|
|
for I := 0 to N_SEARCH - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
Needle := TItem(OL.Get(Idx));
|
|
|
|
|
if OL.IndexOf(Needle) >= 0 then
|
|
|
|
|
Hits := Hits + 1
|
|
|
|
|
end;
|
|
|
|
|
Report('IndexOf x 1000 (hits=' + IntToStr(Hits) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
OL.Free;
|
|
|
|
|
WriteLn
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure BenchGenericListObject;
|
|
|
|
|
var
|
|
|
|
|
L: TList<TObject>;
|
|
|
|
|
I: Integer;
|
|
|
|
|
J: Integer;
|
|
|
|
|
Idx: Integer;
|
|
|
|
|
T0: TInstant;
|
|
|
|
|
Obj: TObject;
|
|
|
|
|
Item: TItem;
|
|
|
|
|
Sum: Int64;
|
|
|
|
|
Hits: Integer;
|
|
|
|
|
Needle: TObject;
|
|
|
|
|
Found: Boolean;
|
|
|
|
|
begin
|
|
|
|
|
WriteLn('TList<TObject> (manual Free)');
|
|
|
|
|
|
|
|
|
|
{ 1. Bulk insert }
|
|
|
|
|
L := TList<TObject>.Create;
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
for I := 0 to N_BIG - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Item := TItem.Create;
|
|
|
|
|
Item.FValue := I;
|
|
|
|
|
L.Add(Item)
|
|
|
|
|
end;
|
|
|
|
|
Report('insert 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 2. Sequential read }
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to L.Count - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Item := TItem(L.Get(I));
|
|
|
|
|
Sum := Sum + Item.FValue
|
|
|
|
|
end;
|
|
|
|
|
Report('sequential Get(i) x Count (sum=' + IntToStr(Sum) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 3. Random read }
|
|
|
|
|
SeedRand(42);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Sum := 0;
|
|
|
|
|
for I := 0 to N_RANDOM - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
Item := TItem(L.Get(Idx));
|
|
|
|
|
Sum := Sum + Item.FValue
|
|
|
|
|
end;
|
|
|
|
|
Report('random Get(i) x 1_000_000', ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ 4. Hand-rolled IndexOf — pointer equality }
|
|
|
|
|
SeedRand(7);
|
|
|
|
|
T0 := InstantNow;
|
|
|
|
|
Hits := 0;
|
|
|
|
|
for I := 0 to N_SEARCH - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Idx := NextRand mod N_BIG;
|
|
|
|
|
Needle := L.Get(Idx);
|
|
|
|
|
Found := False;
|
|
|
|
|
for J := 0 to L.Count - 1 do
|
|
|
|
|
if L.Get(J) = Needle then
|
|
|
|
|
begin
|
|
|
|
|
Found := True;
|
|
|
|
|
break
|
|
|
|
|
end;
|
|
|
|
|
if Found then
|
|
|
|
|
Hits := Hits + 1
|
|
|
|
|
end;
|
|
|
|
|
Report('hand-rolled scan x 1000 (hits=' + IntToStr(Hits) + ')',
|
|
|
|
|
ElapsedMs(T0));
|
|
|
|
|
|
|
|
|
|
{ Manually release each item since TList<TObject> is not owning. }
|
|
|
|
|
for I := 0 to L.Count - 1 do
|
|
|
|
|
begin
|
|
|
|
|
Obj := L.Get(I);
|
|
|
|
|
Obj.Free
|
|
|
|
|
end;
|
|
|
|
|
L.Free;
|
|
|
|
|
WriteLn
|
|
|
|
|
end;
|
|
|
|
|
|
2026-05-21 12:53:13 +03:00
|
|
|
begin
|
2026-05-21 16:37:22 +03:00
|
|
|
WriteLn('=== Blaise list micro-benchmark ===');
|
2026-05-21 12:53:13 +03:00
|
|
|
WriteLn('N_BIG=' + IntToStr(N_BIG) +
|
|
|
|
|
' N_MID=' + IntToStr(N_MID) +
|
|
|
|
|
' N_RANDOM=' + IntToStr(N_RANDOM) +
|
|
|
|
|
' N_SEARCH=' + IntToStr(N_SEARCH));
|
|
|
|
|
WriteLn;
|
|
|
|
|
BenchTStringList;
|
2026-05-21 16:37:22 +03:00
|
|
|
BenchGenericListString;
|
|
|
|
|
BenchTObjectList;
|
|
|
|
|
BenchGenericListObject
|
2026-05-21 12:53:13 +03:00
|
|
|
end.
|