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