|
@@ -1,4 +1,5 @@
|
|
{$mode objfpc} {$h+} {$typedaddress on} {$modeswitch advancedrecords} {$coperators on} {$modeswitch anonymousfunctions}
|
|
{$mode objfpc} {$h+} {$typedaddress on} {$modeswitch advancedrecords} {$coperators on} {$modeswitch anonymousfunctions}
|
|
|
|
+{$modeswitch duplicatelocals}
|
|
|
|
|
|
uses
|
|
uses
|
|
SysUtils, Generics.Collections, Generics.Defaults;
|
|
SysUtils, Generics.Collections, Generics.Defaults;
|
|
@@ -13,6 +14,55 @@ var
|
|
temp := a; a := b; b := temp;
|
|
temp := a; a := b; b := temp;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+type
|
|
|
|
+ generic TTracingComparer<Ty> = class(specialize TComparer<Ty>)
|
|
|
|
+ orig: specialize IComparer<Ty>;
|
|
|
|
+ count: uint64;
|
|
|
|
+ constructor Create(const orig: specialize IComparer<Ty>);
|
|
|
|
+ function Compare(const a, b: Ty): integer; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ constructor TTracingComparer.Create(const orig: specialize IComparer<Ty>);
|
|
|
|
+ begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ self.orig := orig;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function TTracingComparer.Compare(const a, b: Ty): integer;
|
|
|
|
+ begin
|
|
|
|
+ result := orig.Compare(a, b);
|
|
|
|
+ count += 1;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+type
|
|
|
|
+ // https://igoro.com/archive/quicksort-killer/
|
|
|
|
+ // Will work against wide range of qsort implementations.
|
|
|
|
+ TQSortKillerComparer = class(specialize TComparer<SizeInt>)
|
|
|
|
+ keys: array of int32; { TDictionary is a lot slower... }
|
|
|
|
+ candidate, nKeys: int32;
|
|
|
|
+ constructor Create(arrayLen: SizeInt);
|
|
|
|
+ function Compare(const a, b: SizeInt): integer; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ constructor TQSortKillerComparer.Create(arrayLen: SizeInt);
|
|
|
|
+ begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ SetLength(keys, arrayLen);
|
|
|
|
+ FillChar(pInt32(keys)^, length(keys) * sizeof(keys[0]), byte(-1));
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function TQSortKillerComparer.Compare(const a, b: SizeInt): integer;
|
|
|
|
+ begin
|
|
|
|
+ if keys[a] and keys[b] < 0 then
|
|
|
|
+ begin
|
|
|
|
+ if a = candidate then keys[a] := nKeys else keys[b] := nKeys;
|
|
|
|
+ nKeys += 1;
|
|
|
|
+ end;
|
|
|
|
+ if keys[a] < 0 then begin candidate := a; exit(1); end;
|
|
|
|
+ if keys[b] < 0 then begin candidate := b; exit(-1); end;
|
|
|
|
+ result := keys[a] - keys[b];
|
|
|
|
+ end;
|
|
|
|
+
|
|
type
|
|
type
|
|
generic SortBenchmark<Ty> = record
|
|
generic SortBenchmark<Ty> = record
|
|
type
|
|
type
|
|
@@ -20,10 +70,7 @@ type
|
|
TyArray = array of Ty;
|
|
TyArray = array of Ty;
|
|
|
|
|
|
class procedure Run(create: CreateProc; const tyPlural: string; lenMul: double); static;
|
|
class procedure Run(create: CreateProc; const tyPlural: string; lenMul: double); static;
|
|
- class procedure BenchSort(const src, ref: array of Ty; var prevTime: double); static;
|
|
|
|
-
|
|
|
|
- // Built against specific QSort implementation that uses median of 3 elements: L, R, and (L + R + 1) div 2,
|
|
|
|
- // and WON'T KILL ANY OTHER.
|
|
|
|
|
|
+ class procedure BenchSort(const src, ref: array of Ty; var prevComparisons: uint64); static;
|
|
// 'ref' must be sorted and contain no duplicates.
|
|
// 'ref' must be sorted and contain no duplicates.
|
|
class function BuildQSortKiller(const ref: array of Ty): TyArray; static;
|
|
class function BuildQSortKiller(const ref: array of Ty): TyArray; static;
|
|
end;
|
|
end;
|
|
@@ -39,14 +86,14 @@ type
|
|
cmp: specialize IComparer<Ty>;
|
|
cmp: specialize IComparer<Ty>;
|
|
srcOrder: OrderEnum;
|
|
srcOrder: OrderEnum;
|
|
msg: string;
|
|
msg: string;
|
|
- prevTime: double;
|
|
|
|
|
|
+ prevComparisons: uint64;
|
|
begin
|
|
begin
|
|
writeln('--- ', tyPlural, ' ---', LineEnding);
|
|
writeln('--- ', tyPlural, ' ---', LineEnding);
|
|
|
|
|
|
for srcOrder in OrderEnum do
|
|
for srcOrder in OrderEnum do
|
|
begin
|
|
begin
|
|
writeln('Order: ', OrderNames[srcOrder], '.');
|
|
writeln('Order: ', OrderNames[srcOrder], '.');
|
|
- prevTime := -1;
|
|
|
|
|
|
+ prevComparisons := uint64(-1);
|
|
for lenBase in specialize TArray<SizeInt>.Create(10 * 1000, 20 * 1000, 40 * 1000) do
|
|
for lenBase in specialize TArray<SizeInt>.Create(10 * 1000, 20 * 1000, 40 * 1000) do
|
|
begin
|
|
begin
|
|
len := round(lenMul * lenBase);
|
|
len := round(lenMul * lenBase);
|
|
@@ -79,7 +126,7 @@ type
|
|
|
|
|
|
WriteStr(msg, 'n = ', len, ': ');
|
|
WriteStr(msg, 'n = ', len, ': ');
|
|
write(msg.PadRight(12));
|
|
write(msg.PadRight(12));
|
|
- BenchSort(src, ref, prevTime);
|
|
|
|
|
|
+ BenchSort(src, ref, prevComparisons);
|
|
end;
|
|
end;
|
|
writeln;
|
|
writeln;
|
|
end;
|
|
end;
|
|
@@ -88,62 +135,50 @@ type
|
|
class function SortBenchmark.BuildQSortKiller(const ref: array of Ty): TyArray;
|
|
class function SortBenchmark.BuildQSortKiller(const ref: array of Ty): TyArray;
|
|
var
|
|
var
|
|
ris: array of SizeInt;
|
|
ris: array of SizeInt;
|
|
- i, nr: SizeInt;
|
|
|
|
|
|
+ i: SizeInt;
|
|
|
|
+ cmpRef: specialize IComparer<SizeInt>;
|
|
begin
|
|
begin
|
|
- SetLength((@ris)^, length(ref)); // Swaps that QSort would perform are tracked here, to build the worst case possible. >:3
|
|
|
|
|
|
+ SetLength((@ris)^, length(ref));
|
|
for i := 0 to High(ris) do ris[i] := i;
|
|
for i := 0 to High(ris) do ris[i] := i;
|
|
-
|
|
|
|
|
|
+ cmpRef := TQSortKillerComparer.Create(length(ref));
|
|
|
|
+ specialize TArrayHelper<SizeInt>.Sort(ris, cmpRef);
|
|
SetLength((@result)^, length(ref));
|
|
SetLength((@result)^, length(ref));
|
|
- i := 0; nr := length(ref);
|
|
|
|
- while i < nr do
|
|
|
|
- begin
|
|
|
|
- result[ris[i]] := ref[i];
|
|
|
|
- if i + 1 = nr then break;
|
|
|
|
- specialize Swap<SizeInt>(ris[i + 1], ris[i + SizeInt(SizeUint(nr - i) div 2)]);
|
|
|
|
- result[ris[i + 1]] := ref[i + 1];
|
|
|
|
- i += 2;
|
|
|
|
- end;
|
|
|
|
|
|
+ for i := 0 to High(result) do result[ris[i]] := ref[i];
|
|
end;
|
|
end;
|
|
|
|
|
|
- class procedure SortBenchmark.BenchSort(const src, ref: array of Ty; var prevTime: double);
|
|
|
|
|
|
+ class procedure SortBenchmark.BenchSort(const src, ref: array of Ty; var prevComparisons: uint64);
|
|
var
|
|
var
|
|
arr: TyArray;
|
|
arr: TyArray;
|
|
- startTime: TDateTime;
|
|
|
|
- time, timePassed: double;
|
|
|
|
i: SizeInt;
|
|
i: SizeInt;
|
|
- reps: cardinal;
|
|
|
|
|
|
+ cmp: specialize TTracingComparer<Ty>;
|
|
|
|
+ cmpRef: specialize IComparer<Ty>;
|
|
|
|
+ prevCount: uint64;
|
|
begin
|
|
begin
|
|
- startTime := Now;
|
|
|
|
- reps := 0;
|
|
|
|
- repeat
|
|
|
|
- arr := Copy(src);
|
|
|
|
- specialize TArrayHelper<Ty>.Sort(arr);
|
|
|
|
- timePassed := (Now - startTime) * SecsPerDay;
|
|
|
|
- reps += 1;
|
|
|
|
- until not (timePassed < 3);
|
|
|
|
-
|
|
|
|
- time := timePassed / reps;
|
|
|
|
- write(time * 1e3:0:1, ' ms/sort');
|
|
|
|
- if prevTime > 0 then write(' (', time / prevTime:0:1, 'x from previous)');
|
|
|
|
- if (prevTime > 0) and (time / prevTime > 3) then
|
|
|
|
- begin
|
|
|
|
- writeln;
|
|
|
|
- writeln('Potentially bad sorting algorithm behaviour');
|
|
|
|
- { causes too many false negative
|
|
|
|
- halt(1);
|
|
|
|
- }
|
|
|
|
- end;
|
|
|
|
- prevTime := time;
|
|
|
|
- write(', ');
|
|
|
|
|
|
+ cmp := specialize TTracingComparer<Ty>.Create(specialize TComparer<Ty>.Default);
|
|
|
|
+ cmpRef := cmp;
|
|
|
|
+
|
|
|
|
+ arr := Copy(src);
|
|
|
|
+ specialize TArrayHelper<Ty>.Sort(arr, cmpRef);
|
|
|
|
+ prevCount := prevComparisons;
|
|
|
|
+ prevComparisons := cmp.count;
|
|
|
|
+ write(cmp.count, ' comparisons');
|
|
|
|
+ if prevCount <> uint64(-1) then write(' (', cmp.count / prevCount:0:1, 'x from previous)');
|
|
|
|
|
|
for i := 0 to High(ref) do
|
|
for i := 0 to High(ref) do
|
|
if arr[i] <> ref[i] then
|
|
if arr[i] <> ref[i] then
|
|
begin
|
|
begin
|
|
- writeln('FAIL @ ', i, ' / ', length(ref));
|
|
|
|
|
|
+ writeln(', FAIL @ ', i, ' / ', length(ref));
|
|
anythingWrong := true;
|
|
anythingWrong := true;
|
|
exit;
|
|
exit;
|
|
end;
|
|
end;
|
|
- writeln('OK');
|
|
|
|
|
|
+
|
|
|
|
+ if (prevCount <> uint64(-1)) and (cmp.count > 2 * prevCount + prevCount div 2 + 5 * length(src) + 1000) then
|
|
|
|
+ begin
|
|
|
|
+ writeln(', potentially bad sorting algorithm behaviour');
|
|
|
|
+ anythingWrong := true;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ writeln(', OK');
|
|
end;
|
|
end;
|
|
|
|
|
|
begin
|
|
begin
|
|
@@ -166,7 +201,7 @@ begin
|
|
|
|
|
|
if anythingWrong then
|
|
if anythingWrong then
|
|
begin
|
|
begin
|
|
- writeln(LineEnding, 'Something was wrong, see above.');
|
|
|
|
|
|
+ writeln('Something was wrong, see above.');
|
|
halt(2);
|
|
halt(2);
|
|
end;
|
|
end;
|
|
end.
|
|
end.
|