test(bench): merge bench_strings + bench_objects into bench_lists
The multi-instance generic miscompile that forced the two-program
split is fixed (b6736d3), so the four workloads now run in a single
program: TStringList, TList<String>, TObjectList, TList<TObject>.
Add a fresh results entry on the post-fix compiler; timings are
within noise of the split run on the same hardware.
This commit is contained in:
parent
b6736d3925
commit
07b9b8ddbd
|
|
@ -6,32 +6,29 @@
|
|||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
program BenchStrings;
|
||||
program BenchLists;
|
||||
|
||||
{ Temporary micro-benchmark — TStringList vs TList<String>.
|
||||
{ Micro-benchmark — TStringList vs TList<String> and TObjectList vs
|
||||
TList<TObject>.
|
||||
|
||||
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
|
||||
4. Linear search — 1000 lookups on a 100_000-item list
|
||||
(TList<T> has no IndexOf — hand-rolled scan)
|
||||
5. for..in iterate — checksum across all items
|
||||
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)
|
||||
|
||||
All times are wall-clock milliseconds via TInstant.Subtract.
|
||||
Run from the project root:
|
||||
|
||||
compiler/target/blaise --source tests/bench_strings.pas --output /tmp/bench_strings
|
||||
/tmp/bench_strings
|
||||
|
||||
NOTE: this benchmark and tests/bench_objects.pas are split because the
|
||||
compiler currently miscompiles a program that instantiates the same
|
||||
generic class with two different concrete type arguments
|
||||
(e.g. TList<String> and TList<TObject> in the same program) — a
|
||||
pre-existing limitation tracked separately. }
|
||||
compiler/target/blaise --source tests/bench_lists.pas --output /tmp/bench_lists
|
||||
/tmp/bench_lists
|
||||
}
|
||||
|
||||
uses
|
||||
Classes,
|
||||
Contnrs,
|
||||
Generics.Collections,
|
||||
DateUtils,
|
||||
SysUtils;
|
||||
|
|
@ -42,6 +39,11 @@ const
|
|||
N_RANDOM = 1000000;
|
||||
N_SEARCH = 1000;
|
||||
|
||||
type
|
||||
TItem = class
|
||||
FValue: Integer;
|
||||
end;
|
||||
|
||||
var
|
||||
GRand: Integer;
|
||||
|
||||
|
|
@ -170,14 +172,14 @@ end;
|
|||
|
||||
procedure BenchGenericListString;
|
||||
var
|
||||
L: TList<String>;
|
||||
I: Integer;
|
||||
Idx: Integer;
|
||||
J: Integer;
|
||||
T0: TInstant;
|
||||
S: string;
|
||||
Sum: Int64;
|
||||
Hits: Integer;
|
||||
L: TList<String>;
|
||||
I: Integer;
|
||||
Idx: Integer;
|
||||
J: Integer;
|
||||
T0: TInstant;
|
||||
S: string;
|
||||
Sum: Int64;
|
||||
Hits: Integer;
|
||||
Found: Boolean;
|
||||
begin
|
||||
WriteLn('TList<String>');
|
||||
|
|
@ -249,13 +251,161 @@ begin
|
|||
WriteLn
|
||||
end;
|
||||
|
||||
procedure BenchTObjectList;
|
||||
var
|
||||
OL: TObjectList;
|
||||
I: Integer;
|
||||
Idx: Integer;
|
||||
T0: TInstant;
|
||||
Item: TItem;
|
||||
Sum: Int64;
|
||||
Hits: Integer;
|
||||
Needle: TItem;
|
||||
begin
|
||||
WriteLn('=== Blaise list micro-benchmark - strings ===');
|
||||
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;
|
||||
|
||||
begin
|
||||
WriteLn('=== Blaise list micro-benchmark ===');
|
||||
WriteLn('N_BIG=' + IntToStr(N_BIG) +
|
||||
' N_MID=' + IntToStr(N_MID) +
|
||||
' N_RANDOM=' + IntToStr(N_RANDOM) +
|
||||
' N_SEARCH=' + IntToStr(N_SEARCH));
|
||||
WriteLn;
|
||||
BenchTStringList;
|
||||
BenchGenericListString
|
||||
BenchGenericListString;
|
||||
BenchTObjectList;
|
||||
BenchGenericListObject
|
||||
end.
|
||||
|
|
@ -2,14 +2,13 @@ Blaise list micro-benchmark — results log
|
|||
==========================================
|
||||
|
||||
Each entry records the median of two consecutive runs of
|
||||
tests/bench_strings.pas and tests/bench_objects.pas, taken on the
|
||||
machine identified in the entry header. Add new entries below the
|
||||
previous one (newest at the bottom) so the file forms a chronological
|
||||
trend.
|
||||
tests/bench_lists.pas, taken on the machine identified in the entry
|
||||
header. Add new entries below the previous one (newest at the
|
||||
bottom) so the file forms a chronological trend.
|
||||
|
||||
Workloads:
|
||||
- bench_strings: TStringList vs TList<String>
|
||||
- bench_objects: TObjectList vs TList<TObject>
|
||||
Workloads (all in one program):
|
||||
- TStringList vs TList<String>
|
||||
- TObjectList vs TList<TObject>
|
||||
|
||||
Phases (all ms wall-clock):
|
||||
insert 1,000,000 Adds (item_<i> strings or fresh TItem instances)
|
||||
|
|
@ -57,3 +56,31 @@ Phases (all ms wall-clock):
|
|||
(e.g. TList<String> and TList<TObject> in one program) — that is
|
||||
why the benchmark is split into two files. (Fixed in a later
|
||||
commit via per-instance AST body cloning.)
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
2026-05-21 — Graeme @ AMD Ryzen 7 5800X (Linux 6.14)
|
||||
Blaise compiler v0.9.0-dev
|
||||
Commit: b6736d3 (fix semantic: clone method bodies per generic instance)
|
||||
|
||||
--- merged into a single program (tests/bench_lists.pas) ---
|
||||
TStringList TList<String>
|
||||
insert 1_000_000 99 ms 89 ms
|
||||
sequential Get(i) x Count 10 ms 10 ms
|
||||
random Get(i) x 1_000_000 20 ms 19 ms
|
||||
for..in over 1_000_000 15 ms 11 ms
|
||||
IndexOf / hand-rolled scan x 1000 681 ms 287 ms
|
||||
|
||||
TObjectList TList<TObject>
|
||||
insert 1_000_000 36 ms 40 ms
|
||||
sequential Get(i) x Count 4 ms 6 ms
|
||||
random Get(i) x 1_000_000 9 ms 13 ms
|
||||
IndexOf / hand-rolled scan x 1000 16 ms 131 ms
|
||||
|
||||
Notes:
|
||||
* Multi-instance generic miscompile is fixed — TList<String> and
|
||||
TList<TObject> now co-exist in one program, so the two split
|
||||
benchmarks have been merged into tests/bench_lists.pas.
|
||||
* Numbers are within noise of the previous split-program run on
|
||||
the same hardware; combining the workloads does not perturb
|
||||
timings.
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
{
|
||||
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.
|
||||
}
|
||||
|
||||
program BenchObjects;
|
||||
|
||||
{ Temporary micro-benchmark — TObjectList vs TList<TObject>.
|
||||
|
||||
For each container we measure:
|
||||
1. Bulk insert — 1_000_000 Adds of fresh TItem instances
|
||||
2. Sequential read — Get(i) + read FValue for every index
|
||||
3. Random read — 1_000_000 indexed reads in a pseudo-random pattern
|
||||
4. Linear search — 1000 lookups on the 1M-item list (we look for
|
||||
objects we know are present; pointer equality)
|
||||
5. for..in iterate — checksum across all items (TObjectList only;
|
||||
TList<TObject>.GetEnumerator returns Current as
|
||||
the element type which round-trips fine here)
|
||||
|
||||
All times are wall-clock milliseconds via TInstant.Subtract.
|
||||
Run from the project root:
|
||||
|
||||
compiler/target/blaise --source tests/bench_objects.pas --output /tmp/bench_objects
|
||||
/tmp/bench_objects
|
||||
|
||||
NOTE: split from tests/bench_strings.pas — see that file's header for
|
||||
the compiler limitation that forces the split. }
|
||||
|
||||
uses
|
||||
Contnrs,
|
||||
Generics.Collections,
|
||||
DateUtils,
|
||||
SysUtils;
|
||||
|
||||
const
|
||||
N_BIG = 1000000;
|
||||
N_RANDOM = 1000000;
|
||||
N_SEARCH = 1000;
|
||||
|
||||
type
|
||||
TItem = class
|
||||
FValue: Integer;
|
||||
end;
|
||||
|
||||
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 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;
|
||||
|
||||
begin
|
||||
WriteLn('=== Blaise list micro-benchmark - objects ===');
|
||||
WriteLn('N_BIG=' + IntToStr(N_BIG) +
|
||||
' N_RANDOM=' + IntToStr(N_RANDOM) +
|
||||
' N_SEARCH=' + IntToStr(N_SEARCH));
|
||||
WriteLn;
|
||||
BenchTObjectList;
|
||||
BenchGenericListObject
|
||||
end.
|
||||
Loading…
Reference in a new issue