tqsort_killer.pp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. {$mode objfpc} {$h+} {$typedaddress on} {$modeswitch advancedrecords} {$coperators on} {$modeswitch anonymousfunctions}
  2. uses
  3. SysUtils, Generics.Collections, Generics.Defaults;
  4. var
  5. anythingWrong: boolean = false;
  6. generic procedure Swap<Ty>(var a, b: Ty);
  7. var
  8. temp: Ty;
  9. begin
  10. temp := a; a := b; b := temp;
  11. end;
  12. type
  13. generic SortBenchmark<Ty> = record
  14. type
  15. CreateProc = function(id: SizeUint): Ty;
  16. TyArray = array of Ty;
  17. class procedure Run(create: CreateProc; const tyPlural: string; lenMul: double); static;
  18. class procedure BenchSort(const src, ref: array of Ty; var prevTime: double); static;
  19. // Built against specific QSort implementation that uses median of 3 elements: L, R, and (L + R + 1) div 2,
  20. // and WON'T KILL ANY OTHER.
  21. // 'ref' must be sorted and contain no duplicates.
  22. class function BuildQSortKiller(const ref: array of Ty): TyArray; static;
  23. end;
  24. class procedure SortBenchmark.Run(create: CreateProc; const tyPlural: string; lenMul: double);
  25. type
  26. OrderEnum = (RandomOrder, QSortKillerOrder);
  27. const
  28. OrderNames: array[OrderEnum] of string = ('random', 'QSort killer');
  29. var
  30. ref, src: TyArray;
  31. i, lenBase, len: SizeInt;
  32. cmp: specialize IComparer<Ty>;
  33. srcOrder: OrderEnum;
  34. msg: string;
  35. prevTime: double;
  36. begin
  37. writeln('--- ', tyPlural, ' ---', LineEnding);
  38. for srcOrder in OrderEnum do
  39. begin
  40. writeln('Order: ', OrderNames[srcOrder], '.');
  41. prevTime := -1;
  42. for lenBase in specialize TArray<SizeInt>.Create(10 * 1000, 20 * 1000, 40 * 1000) do
  43. begin
  44. len := round(lenMul * lenBase);
  45. SetLength((@ref)^, len);
  46. cmp := specialize TComparer<Ty>.Default;
  47. for i := 0 to len - 1 do
  48. begin
  49. ref[i] := create(i);
  50. if (i > 0) and (cmp.Compare(ref[i - 1], ref[i]) >= 0) then
  51. begin
  52. writeln('''create'' callback must return ', tyPlural, ' in strictly ascending order.');
  53. anythingWrong := true;
  54. exit;
  55. end;
  56. end;
  57. case srcOrder of
  58. RandomOrder:
  59. begin
  60. RandSeed := 1;
  61. src := Copy(ref);
  62. for i := len - 1 downto 1 do
  63. specialize Swap<Ty>(src[i], src[random(int64(i))]);
  64. end;
  65. QSortKillerOrder:
  66. src := BuildQSortKiller(ref);
  67. end;
  68. WriteStr(msg, 'n = ', len, ': ');
  69. write(msg.PadRight(12));
  70. BenchSort(src, ref, prevTime);
  71. end;
  72. writeln;
  73. end;
  74. end;
  75. class function SortBenchmark.BuildQSortKiller(const ref: array of Ty): TyArray;
  76. var
  77. ris: array of SizeInt;
  78. i, nr: SizeInt;
  79. begin
  80. SetLength((@ris)^, length(ref)); // Swaps that QSort would perform are tracked here, to build the worst case possible. >:3
  81. for i := 0 to High(ris) do ris[i] := i;
  82. SetLength((@result)^, length(ref));
  83. i := 0; nr := length(ref);
  84. while i < nr do
  85. begin
  86. result[ris[i]] := ref[i];
  87. if i + 1 = nr then break;
  88. specialize Swap<SizeInt>(ris[i + 1], ris[i + SizeInt(SizeUint(nr - i) div 2)]);
  89. result[ris[i + 1]] := ref[i + 1];
  90. i += 2;
  91. end;
  92. end;
  93. class procedure SortBenchmark.BenchSort(const src, ref: array of Ty; var prevTime: double);
  94. var
  95. arr: TyArray;
  96. startTime: TDateTime;
  97. time, timePassed: double;
  98. i: SizeInt;
  99. reps: cardinal;
  100. begin
  101. startTime := Now;
  102. reps := 0;
  103. repeat
  104. arr := Copy(src);
  105. specialize TArrayHelper<Ty>.Sort(arr);
  106. timePassed := (Now - startTime) * SecsPerDay;
  107. reps += 1;
  108. until not (timePassed < 0.2);
  109. time := timePassed / reps;
  110. write(time * 1e3:0:1, ' ms/sort');
  111. if prevTime > 0 then write(' (', time / prevTime:0:1, 'x from previous)');
  112. if (prevTime > 0) and (time / prevTime > 3) then
  113. begin
  114. writeln;
  115. writeln('Bad sorting algorithm behaviour');
  116. halt(1);
  117. end;
  118. prevTime := time;
  119. write(', ');
  120. for i := 0 to High(ref) do
  121. if arr[i] <> ref[i] then
  122. begin
  123. writeln('FAIL @ ', i, ' / ', length(ref));
  124. anythingWrong := true;
  125. exit;
  126. end;
  127. writeln('OK');
  128. end;
  129. begin
  130. specialize SortBenchmark<string>.Run(
  131. function(id: SizeUint): string
  132. begin
  133. SetLength((@result)^, 5);
  134. result[5] := char(ord('A') + id mod 26); id := id div 26;
  135. result[4] := char(ord('A') + id mod 26); id := id div 26;
  136. result[3] := char(ord('A') + id mod 26); id := id div 26;
  137. result[2] := char(ord('A') + id mod 26); id := id div 26;
  138. result[1] := char(ord('A') + id mod 26);
  139. end, 'strings', 0.15);
  140. specialize SortBenchmark<single>.Run(
  141. function(id: SizeUint): single
  142. begin
  143. result := -1000 + id / 1000;
  144. end, 'float32''s', 1.0);
  145. if anythingWrong then
  146. begin
  147. writeln(LineEnding, 'Something was wrong, see above.');
  148. halt(2);
  149. end;
  150. end.