tqsort_killer.pp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. {$mode objfpc}
  2. {$h+}
  3. {$typedaddress on}
  4. {$modeswitch advancedrecords}
  5. {$coperators on}
  6. {$modeswitch anonymousfunctions}
  7. {$modeswitch duplicatelocals}
  8. uses
  9. {$ifdef unix}cwstring,{$endif}
  10. SysUtils, Generics.Collections, Generics.Defaults;
  11. var
  12. anythingWrong: boolean = false;
  13. generic procedure Swap<Ty>(var a, b: Ty);
  14. var
  15. temp: Ty;
  16. begin
  17. temp := a; a := b; b := temp;
  18. end;
  19. type
  20. generic TTracingComparer<Ty> = class(specialize TComparer<Ty>)
  21. orig: specialize IComparer<Ty>;
  22. count: uint64;
  23. constructor Create(const orig: specialize IComparer<Ty>);
  24. function Compare(const a, b: Ty): integer; override;
  25. end;
  26. constructor TTracingComparer.Create(const orig: specialize IComparer<Ty>);
  27. begin
  28. inherited Create;
  29. self.orig := orig;
  30. end;
  31. function TTracingComparer.Compare(const a, b: Ty): integer;
  32. begin
  33. result := orig.Compare(a, b);
  34. count += 1;
  35. end;
  36. type
  37. // https://igoro.com/archive/quicksort-killer/
  38. // Will work against wide range of qsort implementations.
  39. TQSortKillerComparer = class(specialize TComparer<SizeInt>)
  40. keys: array of int32; { TDictionary is a lot slower... }
  41. candidate, nKeys: int32;
  42. constructor Create(arrayLen: SizeInt);
  43. function Compare(const a, b: SizeInt): integer; override;
  44. end;
  45. constructor TQSortKillerComparer.Create(arrayLen: SizeInt);
  46. begin
  47. inherited Create;
  48. SetLength(keys, arrayLen);
  49. FillChar(pInt32(keys)^, length(keys) * sizeof(keys[0]), byte(-1));
  50. end;
  51. function TQSortKillerComparer.Compare(const a, b: SizeInt): integer;
  52. begin
  53. if keys[a] and keys[b] < 0 then
  54. begin
  55. if a = candidate then keys[a] := nKeys else keys[b] := nKeys;
  56. nKeys += 1;
  57. end;
  58. if keys[a] < 0 then begin candidate := a; exit(1); end;
  59. if keys[b] < 0 then begin candidate := b; exit(-1); end;
  60. result := keys[a] - keys[b];
  61. end;
  62. type
  63. generic SortBenchmark<Ty> = record
  64. type
  65. CreateProc = function(id: SizeUint): Ty;
  66. TyArray = array of Ty;
  67. class procedure Run(create: CreateProc; const tyPlural: string; lenMul: double); static;
  68. class procedure BenchSort(const src, ref: array of Ty; var prevComparisons: uint64); static;
  69. // 'ref' must be sorted and contain no duplicates.
  70. class function BuildQSortKiller(const ref: array of Ty): TyArray; static;
  71. end;
  72. class procedure SortBenchmark.Run(create: CreateProc; const tyPlural: string; lenMul: double);
  73. type
  74. OrderEnum = (RandomOrder, QSortKillerOrder);
  75. const
  76. OrderNames: array[OrderEnum] of string = ('random', 'QSort killer');
  77. var
  78. ref, src: TyArray;
  79. i, lenBase, len: SizeInt;
  80. cmp: specialize IComparer<Ty>;
  81. srcOrder: OrderEnum;
  82. msg: string;
  83. prevComparisons: uint64;
  84. begin
  85. writeln('--- ', tyPlural, ' ---', LineEnding);
  86. for srcOrder in OrderEnum do
  87. begin
  88. writeln('Order: ', OrderNames[srcOrder], '.');
  89. prevComparisons := uint64(-1);
  90. for lenBase in specialize TArray<SizeInt>.Create(10 * 1000, 20 * 1000, 40 * 1000) do
  91. begin
  92. len := round(lenMul * lenBase);
  93. SetLength((@ref)^, len);
  94. cmp := specialize TComparer<Ty>.Default;
  95. for i := 0 to len - 1 do
  96. begin
  97. ref[i] := create(i);
  98. if (i > 0) and (cmp.Compare(ref[i - 1], ref[i]) >= 0) then
  99. begin
  100. writeln('''create'' callback must return ', tyPlural, ' in strictly ascending order.');
  101. anythingWrong := true;
  102. exit;
  103. end;
  104. end;
  105. case srcOrder of
  106. RandomOrder:
  107. begin
  108. RandSeed := 1;
  109. src := Copy(ref);
  110. for i := len - 1 downto 1 do
  111. specialize Swap<Ty>(src[i], src[random(int64(i))]);
  112. end;
  113. QSortKillerOrder:
  114. src := BuildQSortKiller(ref);
  115. end;
  116. WriteStr(msg, 'n = ', len, ': ');
  117. write(msg.PadRight(12));
  118. BenchSort(src, ref, prevComparisons);
  119. end;
  120. writeln;
  121. end;
  122. end;
  123. class function SortBenchmark.BuildQSortKiller(const ref: array of Ty): TyArray;
  124. var
  125. ris: array of SizeInt;
  126. i: SizeInt;
  127. cmpRef: specialize IComparer<SizeInt>;
  128. begin
  129. SetLength((@ris)^, length(ref));
  130. for i := 0 to High(ris) do ris[i] := i;
  131. cmpRef := TQSortKillerComparer.Create(length(ref));
  132. specialize TArrayHelper<SizeInt>.Sort(ris, cmpRef);
  133. SetLength((@result)^, length(ref));
  134. for i := 0 to High(result) do result[ris[i]] := ref[i];
  135. end;
  136. class procedure SortBenchmark.BenchSort(const src, ref: array of Ty; var prevComparisons: uint64);
  137. var
  138. arr: TyArray;
  139. i: SizeInt;
  140. cmp: specialize TTracingComparer<Ty>;
  141. cmpRef: specialize IComparer<Ty>;
  142. prevCount: uint64;
  143. begin
  144. cmp := specialize TTracingComparer<Ty>.Create(specialize TComparer<Ty>.Default);
  145. cmpRef := cmp;
  146. arr := Copy(src);
  147. specialize TArrayHelper<Ty>.Sort(arr, cmpRef);
  148. prevCount := prevComparisons;
  149. prevComparisons := cmp.count;
  150. write(cmp.count, ' comparisons');
  151. if prevCount <> uint64(-1) then write(' (', cmp.count / prevCount:0:1, 'x from previous)');
  152. for i := 0 to High(ref) do
  153. if arr[i] <> ref[i] then
  154. begin
  155. writeln(', FAIL @ ', i, ' / ', length(ref));
  156. anythingWrong := true;
  157. exit;
  158. end;
  159. if (prevCount <> uint64(-1)) and (cmp.count > 2 * prevCount + prevCount div 2 + 5 * length(src) + 1000) then
  160. begin
  161. writeln(', potentially bad sorting algorithm behaviour');
  162. anythingWrong := true;
  163. exit;
  164. end;
  165. writeln(', OK');
  166. end;
  167. begin
  168. specialize SortBenchmark<string>.Run(
  169. function(id: SizeUint): string
  170. begin
  171. SetLength((@result)^, 5);
  172. result[5] := Char(ord('A') + id mod 26); id := id div 26;
  173. result[4] := Char(ord('A') + id mod 26); id := id div 26;
  174. result[3] := Char(ord('A') + id mod 26); id := id div 26;
  175. result[2] := Char(ord('A') + id mod 26); id := id div 26;
  176. result[1] := Char(ord('A') + id mod 26);
  177. end, 'strings', 0.15);
  178. specialize SortBenchmark<single>.Run(
  179. function(id: SizeUint): single
  180. begin
  181. result := -1000 + id / 1000;
  182. end, 'float32''s', 1.0);
  183. if anythingWrong then
  184. begin
  185. writeln('Something was wrong, see above.');
  186. halt(2);
  187. end;
  188. end.