tune.pl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. #!/usr/bin/perl -w
  2. # Copyright Steven J. Ross 2008 - 2014
  3. # Distributed under the Boost Software License, Version 1.0.
  4. # (See accompanying file LICENSE_1_0.txt or copy at
  5. # http://www.boost.org/LICENSE_1_0.txt)
  6. #
  7. # See http://www.boost.org/libs/sort for library home page.
  8. # A speed and accuracy testing and automated parameter tuning script.
  9. $usage = "usage: tune.pl [-tune] [-real] [-tune_verify] [-verbose] [-multiple_iterations] [-large] [-small] [-windows] [fileSize]\n";
  10. # testing sorting on 40 million elements by default
  11. # don't test on below 2^22 (4 million) elements as that is the minimum
  12. # for max_splits of 11 to be efficient
  13. use File::Compare;
  14. $defFileSize = 5000000;
  15. $loopCount = 1;
  16. $realtimes = 0;
  17. $verifycorrect = 0;
  18. $verbose = 0;
  19. $exename = "spreadsort";
  20. $makename = "../../b2 \-\-tune";
  21. $all = "";
  22. $iter_count = 1;
  23. $debug = 0;
  24. $log = "> .tunelog";
  25. $log2 = "> .tunelog 2>&1";
  26. $diffopt = "-q";
  27. $tune = 0;
  28. # have to change the path for UNIX
  29. $prev_path = $ENV{'PATH'};
  30. $ENV{'PATH'} = '.:'.$prev_path;
  31. for (my $ii = 0; $ii < @ARGV; $ii++) {
  32. my $currArg = $ARGV[$ii];
  33. if ($currArg =~ /^-help$/) {
  34. print STDERR $usage;
  35. exit(0);
  36. }
  37. # verification roughly doubles the runtime of this script,
  38. # but it does make sure that results are correct during tuning
  39. # verification always runs during speed comparisons with std::sort
  40. if ($currArg =~ /^-tune_verify$/) {
  41. $verifycorrect = 1;
  42. # use real times only, don't use weighting and special-case tests
  43. # this saves about 5/6 of the script runtime but results are different
  44. } elsif ($currArg =~ /^-real$/) {
  45. $realtimes = 1;
  46. } elsif ($currArg =~ /^-verbose$/) {
  47. $verbose = 1;
  48. # runs until we converge on a precise set of values
  49. # defaults off because of runtime
  50. } elsif ($currArg =~ /^-multiple_iterations$/) {
  51. $iter_count = 4;
  52. } elsif ($currArg =~ /^-debug$/) {
  53. $debug = 1;
  54. $log = "";
  55. $diffopt = "";
  56. } elsif ($currArg =~ /^-large$/) {
  57. $defFileSize = 20000000;
  58. } elsif ($currArg =~ /^-small$/) {
  59. $defFileSize = 100000;
  60. } elsif ($currArg =~ /^-tune$/) {
  61. $tune = 1;
  62. } elsif ($currArg =~ /^-windows$/) {
  63. $makename = "..\\..\\".$makename;
  64. } elsif ($currArg =~ /^-/) {
  65. print STDERR $usage;
  66. exit(0);
  67. } else {
  68. $defFileSize = $currArg;
  69. }
  70. }
  71. $fileSize = $defFileSize;
  72. print STDOUT "Tuning variables for $exename on vectors with $defFileSize elements\n";
  73. # these are reasonable values
  74. $max_splits = 11;
  75. $log_finishing_count = 31;
  76. $log_min_size = 11;
  77. $log_mean_bin_size = 2;
  78. $float_log_min_size = 10;
  79. $float_log_mean_bin_size = 2;
  80. $float_log_finishing_count = 4;
  81. # this value is a minimum to obtain decent file I/O performance
  82. $min_sort_size = 1000;
  83. $std = "";
  84. print STDOUT "building randomgen\n";
  85. system("$makename randomgen $log");
  86. # Tuning to get convergence, maximum of 4 iterations with multiple iterations
  87. # option set
  88. $changed = 1;
  89. my $ii = 0;
  90. if ($tune) {
  91. for ($ii = 0; $changed and $ii < $iter_count; $ii++) {
  92. $changed = 0;
  93. # Tuning max_splits is not recommended.
  94. #print STDOUT "Tuning max_splits\n";
  95. #TuneVariable(\$max_splits, $log_min_size - $log_mean_bin_size, 17);
  96. print STDOUT "Tuning log of the minimum count for recursion\n";
  97. TuneVariable(\$log_min_size, $log_mean_bin_size + 1, $max_splits + $log_mean_bin_size);
  98. print STDOUT "Tuning log_mean_bin_size\n";
  99. TuneVariable(\$log_mean_bin_size, 0, $log_min_size - 1);
  100. print STDOUT "Tuning log_finishing_size\n";
  101. TuneVariable(\$log_finishing_count, 1, $log_min_size);
  102. # tuning variables for floats
  103. $exename = "floatsort";
  104. print STDOUT "Tuning log of the minimum count for recursion for floats\n";
  105. TuneVariable(\$float_log_min_size, $float_log_mean_bin_size + 1, $max_splits + $float_log_mean_bin_size);
  106. print STDOUT "Tuning float_log_mean_bin_size\n";
  107. TuneVariable(\$float_log_mean_bin_size, 0, $float_log_min_size - 1);
  108. print STDOUT "Tuning float_log_finishing_size\n";
  109. TuneVariable(\$float_log_finishing_count, 1, $float_log_min_size);
  110. $exename = "spreadsort";
  111. }
  112. # After optimizations for large datasets are complete, see how small of a
  113. # dataset can be sped up
  114. print STDOUT "Tuning minimum sorting size\n";
  115. TuneMinSize();
  116. print STDOUT "Writing results\n";
  117. }
  118. # Doing a final run with final settings to compare sort times
  119. # also verifying correctness of results
  120. $verifycorrect = 1;
  121. $loopCount = 1;
  122. $fileSize = $defFileSize;
  123. system("$makename $all $log");
  124. $std = "";
  125. PerfTest("Verifying integer_sort", "spreadsort");
  126. PerfTest("Verifying float_sort", "floatsort");
  127. PerfTest("Verifying string_sort", "stringsort");
  128. PerfTest("Verifying integer_sort with mostly-sorted data", "mostlysorted");
  129. PerfTest("Timing integer_sort on already-sorted data", "alreadysorted");
  130. PerfTest("Verifying integer_sort with rightshift", "rightshift");
  131. PerfTest("Verifying integer_sort with 64-bit integers", "int64");
  132. PerfTest("Verifying integer_sort with separate key and data", "keyplusdata");
  133. PerfTest("Verifying reverse integer_sort", "reverseintsort");
  134. PerfTest("Verifying float_sort with doubles", "double");
  135. PerfTest("Verifying float_sort with shift functor", "shiftfloatsort");
  136. PerfTest("Verifying float_sort with functors", "floatfunctorsort");
  137. PerfTest("Verifying string_sort with indexing functors", "charstringsort");
  138. PerfTest("Verifying string_sort with all functors", "stringfunctorsort");
  139. PerfTest("Verifying reverse_string_sort", "reversestringsort");
  140. PerfTest("Verifying reverse_string_sort with functors",
  141. "reversestringfunctorsort");
  142. PerfTest("Verifying generalized string_sort with multiple keys of different types",
  143. "generalizedstruct");
  144. PerfTest("Verifying boost::sort on its custom-built worst-case distribution",
  145. "binaryalrbreaker");
  146. # clean up once we finish
  147. system("$makename clean $log");
  148. # WINDOWS
  149. system("del spread_sort_out.txt $log2");
  150. system("del standard_sort_out.txt $log2");
  151. system("del input.txt $log2");
  152. system("del *.rsp $log2");
  153. system("del *.manifest $log2");
  154. system("del time.txt $log2");
  155. # UNIX
  156. system("rm -f time.txt $log2");
  157. system("rm -f spread_sort_out.txt $log2");
  158. system("rm -f standard_sort_out.txt $log2");
  159. system("rm -f input.txt $log2");
  160. $ENV{'PATH'} = $prev_path;
  161. # A simple speed test comparing std::sort to
  162. sub PerfTest {
  163. my ($message, $local_exe) = @_;
  164. $exename = $local_exe;
  165. print STDOUT "$message\n";
  166. $lastTime = SumTimes();
  167. print STDOUT "runtime: $lastTime\n";
  168. print STDOUT "std::sort time: $baseTime\n";
  169. $speedup = (($baseTime/$lastTime) - 1) * 100;
  170. print STDOUT "speedup: ".sprintf("%.2f", $speedup)."%\n";
  171. }
  172. # Write an updated constants file as part of tuning.
  173. sub WriteConstants {
  174. # deleting the file
  175. $const_file = 'include/boost/sort/spreadsort/detail/constants.hpp';
  176. @cannot = grep {not unlink} $const_file;
  177. print "$0: could not unlink @cannot\n" if @cannot;
  178. # writing the results back to the original file name
  179. unless(open(CONSTANTS, ">$const_file")) {
  180. print STDERR "Can't open output file: $const_file: $!\n";
  181. exit;
  182. }
  183. print CONSTANTS "//constant definitions for the Boost Sort library\n\n";
  184. print CONSTANTS "// Copyright Steven J. Ross 2001 - 2014\n";
  185. print CONSTANTS "// Distributed under the Boost Software License, Version 1.0.\n";
  186. print CONSTANTS "// (See accompanying file LICENSE_1_0.txt or copy at\n";
  187. print CONSTANTS "// http://www.boost.org/LICENSE_1_0.txt)\n\n";
  188. print CONSTANTS "// See http://www.boost.org/libs/sort for library home page.\n";
  189. print CONSTANTS "#ifndef BOOST_SORT_SPREADSORT_DETAIL_CONSTANTS\n";
  190. print CONSTANTS "#define BOOST_SORT_SPREADSORT_DETAIL_CONSTANTS\n";
  191. print CONSTANTS "namespace boost {\n";
  192. print CONSTANTS "namespace sort {\n";
  193. print CONSTANTS "namespace spreadsort {\n";
  194. print CONSTANTS "namespace detail {\n";
  195. print CONSTANTS "//Tuning constants\n";
  196. print CONSTANTS "//This should be tuned to your processor cache;\n";
  197. print CONSTANTS "//if you go too large you get cache misses on bins\n";
  198. print CONSTANTS "//The smaller this number, the less worst-case memory usage.\n";
  199. print CONSTANTS "//If too small, too many recursions slow down spreadsort\n";
  200. print CONSTANTS "enum { max_splits = $max_splits,\n";
  201. print CONSTANTS "//It's better to have a few cache misses and finish sorting\n";
  202. print CONSTANTS "//than to run another iteration\n";
  203. print CONSTANTS "max_finishing_splits = max_splits + 1,\n";
  204. print CONSTANTS "//Sets the minimum number of items per bin.\n";
  205. print CONSTANTS "int_log_mean_bin_size = $log_mean_bin_size,\n";
  206. print CONSTANTS "//Used to force a comparison-based sorting for small bins, if it's faster.\n";
  207. print CONSTANTS "//Minimum value 1\n";
  208. $log_min_split_count = $log_min_size - $log_mean_bin_size;
  209. print CONSTANTS "int_log_min_split_count = $log_min_split_count,\n";
  210. print CONSTANTS "//This is the minimum split count to use spreadsort when it will finish in one\n";
  211. print CONSTANTS "//iteration. Make this larger the faster std::sort is relative to integer_sort.\n";
  212. print CONSTANTS "int_log_finishing_count = $log_finishing_count,\n";
  213. print CONSTANTS "//Sets the minimum number of items per bin for floating point.\n";
  214. print CONSTANTS "float_log_mean_bin_size = $float_log_mean_bin_size,\n";
  215. print CONSTANTS "//Used to force a comparison-based sorting for small bins, if it's faster.\n";
  216. print CONSTANTS "//Minimum value 1\n";
  217. $float_log_min_split_count = $float_log_min_size - $float_log_mean_bin_size;
  218. print CONSTANTS "float_log_min_split_count = $float_log_min_split_count,\n";
  219. print CONSTANTS "//This is the minimum split count to use spreadsort when it will finish in one\n";
  220. print CONSTANTS "//iteration. Make this larger the faster std::sort is relative to float_sort.\n";
  221. print CONSTANTS "float_log_finishing_count = $float_log_finishing_count,\n";
  222. print CONSTANTS "//There is a minimum size below which it is not worth using spreadsort\n";
  223. print CONSTANTS "min_sort_size = $min_sort_size };\n";
  224. print CONSTANTS "}\n}\n}\n}\n#endif\n";
  225. close CONSTANTS;
  226. system("$makename $exename $log");
  227. }
  228. # Sort the file with both std::sort and boost::sort, verify the results are the
  229. # same, update stdtime with std::sort time, and return boost::sort time.
  230. sub CheckTime {
  231. my $sort_time = 0.0;
  232. my $time_file = "time.txt";
  233. # use the line below on systems that can't overwrite.
  234. #system("rm -f $time_file");
  235. system("$exename $loopCount $std > $time_file");
  236. unless(open(CODE, $time_file)) {
  237. print STDERR "Could not open file: $time_file: $!\n";
  238. exit;
  239. }
  240. while ($line = <CODE>) {
  241. @parts = split("time", $line);
  242. if (@parts > 1) {
  243. $sort_time = $parts[1];
  244. last;
  245. }
  246. }
  247. close(CODE);
  248. # verifying correctness
  249. if (not $std and $verifycorrect) {
  250. system("$exename $loopCount -std > $time_file");
  251. unless(open(CODE, $time_file)) {
  252. print STDERR "Could not open file: $time_file: $!\n";
  253. exit;
  254. }
  255. die "Difference in results\n" unless (compare("boost_sort_out.txt","standard_sort_out.txt") == 0) ;
  256. while ($line = <CODE>) {
  257. @parts = split("time", $line);
  258. if (@parts > 1) {
  259. $stdsingle = $parts[1];
  260. last;
  261. }
  262. }
  263. close(CODE);
  264. }
  265. return $sort_time;
  266. }
  267. # Sum up times for different data distributions. If realtimes is not set,
  268. # larger ranges are given a larger weight.
  269. sub SumTimes {
  270. my $time = 0;
  271. $baseTime = 0.0;
  272. $stdsingle = 0.0;
  273. my $ii = 1;
  274. # if we're only using real times, don't bother with the corner-cases
  275. if ($realtimes) {
  276. $ii = 8;
  277. }
  278. for (; $ii <= 16; $ii++) {
  279. system("randomgen $ii $ii $fileSize");
  280. if ($realtimes) {
  281. $time += CheckTime();
  282. $baseTime += $stdsingle;
  283. } else {
  284. # tests with higher levels of randomness are given
  285. # higher priority in timing results
  286. print STDOUT "trying $ii $ii\n" if $debug;
  287. $time += 2 * $ii * CheckTime();
  288. $baseTime += 2 * $ii * $stdsingle;
  289. if ($ii > 1) {
  290. print STDOUT "trying 1 $ii\n" if $debug;
  291. system("randomgen 1 $ii $fileSize");
  292. $time += $ii * CheckTime();
  293. $baseTime += $ii * $stdsingle;
  294. print STDOUT "trying $ii 1\n" if $debug;
  295. system("randomgen $ii 1 $fileSize");
  296. $time += $ii * CheckTime();
  297. $baseTime += $ii * $stdsingle;
  298. }
  299. }
  300. }
  301. if ($time == 0.0) {
  302. $time = 0.01;
  303. }
  304. return $time;
  305. }
  306. # Tests a range of potential values for a variable, and sets it to the fastest.
  307. sub TuneVariable {
  308. my ($tunevar, $beginval, $endval) = @_;
  309. my $best_val = $$tunevar;
  310. my $besttime = 0;
  311. my $startval = $$tunevar;
  312. for ($$tunevar = $beginval; $$tunevar <= $endval; $$tunevar++) {
  313. WriteConstants();
  314. $sumtime = SumTimes();
  315. # If this value is better, use it. If this is the start value
  316. # and it's just as good, use the startval
  317. if (not $besttime or ($sumtime < $besttime) or (($besttime == $sumtime) and ($$tunevar == $startval))) {
  318. $besttime = $sumtime;
  319. $best_val = $$tunevar;
  320. }
  321. print STDOUT "Value: $$tunevar Time: $sumtime\n" if $verbose;
  322. }
  323. $$tunevar = $best_val;
  324. print STDOUT "Best Value: $best_val\n";
  325. if ($best_val != $startval) {
  326. $changed = 1;
  327. }
  328. }
  329. # Determine the cutoff size below which std::sort is faster.
  330. sub TuneMinSize {
  331. for (; $min_sort_size <= $defFileSize; $min_sort_size *= 2) {
  332. $loopCount = ($defFileSize/$min_sort_size)/10;
  333. $fileSize = $min_sort_size;
  334. WriteConstants();
  335. $std = "";
  336. $sumtime = SumTimes();
  337. $std = "-std";
  338. $stdtime = SumTimes();
  339. print STDOUT "Size: $min_sort_size boost::sort Time: $sumtime std::sort Time: $stdtime\n";
  340. last if ($stdtime > $sumtime);
  341. }
  342. }