Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #571: sort.d

File sort.d, 13.9 kB (added by Deewiant, 1 year ago)

Sort benchmarking module

Line 
1 // File created: 2007-07-12 14:23:49
2
3 import tango.core.Array : sort;
4
5 import tango.io.Stdout;
6
7 import tango.math.Random;
8
9 import regex = tango.text.Regex;
10 import tango.text.Util            : locate;
11 import tango.text.convert.Integer : toUtf8, toInt, atoi;
12
13 import tango.util.time.StopWatch;
14
15 const char[] USAGE =
16 "   {} RANGE RUNS [LENGTHS...]
17
18 Benchmark various sorting algorithms by sorting 32-bit integers.
19
20 Results are printed as three sets of three integers, representing milliseconds.
21 Each set contains the mean, minimum, and maximum times taken sorting. The first
22 set is for a random array, the second for a reversed array, and the third for
23 an already sorted array.
24
25 RANGE is the range of the numbers which will be sorted, for instance -10-20.
26
27 RUNS is the number of times each sort will be performed, to average the time
28 taken.
29
30 LENGTHS... stands for any number of numerical arguments describing the lengths
31 of arrays which will be sorted.
32
33 Each length will be sorted both in already sorted order, in reversed order, and
34 in random order. This random order will be generated only once for each length
35 to make the comparisons as fair as possible.
36
37 If RANGE isn't valid, the default, {} to {}, will be used.
38
39 If RUNS is 0 or not numeric, the default, {} will be used.
40
41 If LENGTHS are not specified, the defaults used will be {}.";
42
43 alias int type;
44
45 size_t[] lengths = [15u, 1000, 100_000, 5_000_000];
46 uint runs = 5;
47 type minRange = -1000,
48      maxRange =  1000;
49
50 void main(char[][] args) {
51     void printUsage() {
52         Stderr.formatln(USAGE, args[0], minRange, maxRange, runs, toString(lengths));
53     }
54
55     size_t l = 0;
56     bool gotLengths = false;
57     foreach (i, arg; args[1..$]) {
58         static bool help(char[] s) { return (regex.find(s, "^(--?|/)([?]|h(e?lp)?)$", "i") != -1); }
59
60         if (arg.help())
61             return printUsage();
62
63         if (i == 0) {
64             auto idx = locate(arg, '-');
65
66             if (idx == arg.length) {
67                 Stderr("Ignoring invalid range '")(arg)("'").newline;
68                 continue;
69             }
70
71             int minr, maxr;
72             try {
73                 minr = toInt(arg[0..idx]);
74                 maxr = toInt(arg[idx+1..$]);
75             } catch {
76                 Stderr("Ignoring invalid range '")(arg)("'").newline;
77                 continue;
78             }
79
80             if (minr <= maxr) {
81                 if (minr)
82                     minRange = minr;
83                 else
84                     Stderr("Ignoring invalid minimum range ")(minr).newline;
85
86                 if (maxr)
87                     maxRange = maxr;
88                 else
89                     Stderr("Ignoring invalid maximum range ")(maxr).newline;
90             } else
91                 Stderr("Ignoring inverted range '")(arg)("'").newline;
92         } else {
93             uint a = atoi(arg);
94             if (a) {
95                 if (i == 1)
96                     runs = a;
97                 else {
98                     if (l == lengths.length)
99                         lengths.length = l * 2;
100
101                     gotLengths = true;
102                     lengths[l++] = a;
103                 }
104             } else {
105                 if (i == 1)
106                     Stderr("Ignoring invalid run count ")(a).newline;
107                 else
108                     Stderr("Ignoring invalid length ")(a).newline;
109             }
110         }
111     }
112     if (gotLengths)
113         lengths.length = l;
114
115     Stdout.formatln(
116         "Using arrays of lengths {} containing numbers in the range {}-{}.",
117
118         toString(lengths),
119         minRange, maxRange
120
121     ).newline.print(
122         "Note that odd lengths will be increased by one for the 'median-of-3 killer' sequences used to test quicksort. "
123         "This means that if a length is odd, one of the arrays will actually have a length one greater than the rest."
124
125     ).newline.newline.format(
126         "Generating {} arrays", 4 * lengths.length
127     );
128
129     type[][4][] sortdata = new type[][4][lengths.length];
130
131     foreach (i, len; lengths) {
132         sortdata[i][0] = new type[len];
133
134         foreach (ref val; sortdata[i][0])
135             val = randomize(minRange, maxRange);
136
137         Stdout('.').flush;
138         sortdata[i][2] = sortdata[i][0].dup.sort;
139         Stdout('.').flush;
140         sortdata[i][1] = sortdata[i][2].dup.reverse;
141         Stdout('.').flush;
142
143         if (len & 1)
144             ++len;
145
146         sortdata[i][3] = new type[len];
147
148         for (size_t j = 1, k = len/2; j <= k; ++j) {
149             if (j & 1) {
150                 sortdata[i][3][j-1] = j;
151                 sortdata[i][3][j]   = j+k;
152             }
153             sortdata[i][3][k+j-1] = 2*j;
154         }
155
156         Stdout('.').flush;
157     }
158
159     Stdout(" done.").newline.newline.formatln(
160         "Each of the {} algorithms will run {} times over 4 arrays of each length, "
161         "for a total of {} passes for each algorithm, "
162         "for a total of {} passes globally.",
163
164         algorithms.length,
165         runs,
166         4 * runs * lengths.length,
167         4 * runs * lengths.length * algorithms.length
168     );
169
170     StopWatch watch;
171
172     void benchmark(char[] name, void function(type[]) algo) {
173         Stdout.newline.print(name).flush;
174
175         // test trivial cases
176         algo([]);
177         algo([1]);
178
179         Interval[4][] sum, min, max;
180
181         sum.length = min.length = max.length = sortdata.length;
182
183         for (size_t i = 0; i < sum.length; ++i) {
184             sum[i][] = 0;
185             min[i][] = Interval.max;
186             max[i][] = Interval.min;
187         }
188
189         for (auto r = runs; r-- > 0;) {
190             foreach (j, set; sortdata) {
191                 void timedsort(size_t i) {
192                     auto a = set[i].dup;
193
194                     watch.start;
195                     algo(a);
196                     Interval t = watch.stop;
197
198                     assert (sorted(a), "Not sorted");
199                     assert (sameContents(a, set[i]), "Array corrupted");
200
201                     sum[j][i] += t;
202
203                     if (t < min[j][i]) min[j][i] = t;
204                     if (t > max[j][i]) max[j][i] = t;
205                 }
206
207                 timedsort(0);
208                 timedsort(1);
209                 timedsort(2);
210                 if (algo !is &tangoSort)
211                     timedsort(3);
212
213                 Stdout('.').flush;
214             }
215             Stdout(' ').flush;
216         }
217
218         Stdout.newline;
219
220         foreach (j, set; sortdata) {
221             auto s = sum[j];
222             auto i = min[j];
223             auto x = max[j];
224
225             const FACTOR = 10_000UL;
226
227             Stdout.formatln(
228                 "{,7}: "
229                 "{,5} {,5} {,5} | "
230                 "{,5} {,5} {,5} | "
231                 "{,5} {,5} {,5} | "
232                 "{,5} {,5} {,5}",
233
234                 set[0].length,
235                 cast(ulong)(s[0] / runs * FACTOR), cast(ulong)(i[0] * FACTOR), cast(ulong)(x[0] * FACTOR),
236                 cast(ulong)(s[1] / runs * FACTOR), cast(ulong)(i[1] * FACTOR), cast(ulong)(x[1] * FACTOR),
237                 cast(ulong)(s[2] / runs * FACTOR), cast(ulong)(i[2] * FACTOR), cast(ulong)(x[2] * FACTOR),
238                 cast(ulong)(s[3] / runs * FACTOR), cast(ulong)(i[3] * FACTOR), cast(ulong)(x[3] * FACTOR)
239             );
240         }
241     }
242
243     foreach (algo; algorithms)
244         benchmark(algo.name, algo.sort);
245 }
246
247 char[] toString(T)(T[] a) {
248     char[] s = "[";
249     foreach (e; a)
250         s ~= toUtf8(e) ~ ",";
251     s[$-1] = ']';
252     return s;
253 }
254
255 int randomize(int min, int max) {
256     auto range = max - min;
257
258     assert (range > 0);
259
260     auto mod = uint.max - uint.max % range;
261
262     uint n;
263     do n = Random.shared.next();
264     while (n >= mod);
265
266     return cast(int)(n % range) + min;
267 }
268
269 bool sorted(type[] a) {
270     if (a.length > 1)
271     foreach (j, n; a[1..$])
272     if (n != a[j] && cmp(n, a[j])) {
273         Stderr.formatln("cmp {} and {} shouldn't be true in {}", a[j], n, toString(a));
274         return false;
275     }
276
277     return true;
278 }
279
280 bool sameContents(type[] a, type[] b) {
281     bool[type] aa;
282
283     foreach (i; a)
284         aa[i] = true;
285
286     foreach (i; b)
287         if (!(i in aa))
288             return false;
289
290     return true;
291 }
292
293 ///////////////////////////////////
294
295 void swap(T)(inout T a, inout T b) {
296     auto
297     t = a;
298     a = b;
299     b = t;
300 }
301
302 bool compare(type a, type b) {
303     return a < b;
304 }
305
306 bool function(type, type) cmp;
307
308 struct Algo {
309     void function(type[]) sort;
310     char[] name;
311 }
312 Algo[] algorithms;
313
314 static this() {
315     cmp = &compare;
316
317     algorithms = [
318         //Algo( &insertionSort, "Insertion sort" )
319         //,Algo( &selectionSort, "Selection sort" )
320
321         //Algo( &combSort11, "Comb sort 11" )
322         //,Algo( &shellSort, "Shell sort" )
323
324         Algo( &heapSort, "Heap sort" )
325         //,Algo( &smoothSort, "Smoothsort" )
326
327         ,Algo( &builtinSort, "Built-in sort (UNFAIR: DOESN'T CALL FUNCTION FOR COMPARISON)" )
328         ,Algo( &tangoSort, "Tango sort" ) // dies due to stack overflow for median of 3 killers
329         ,Algo( &introSort, "Introspective sort" )
330
331         ,Algo( &mergeSort, "Merge sort" )
332     ];
333 }
334
335 void insertionSort(type[] a) {
336     for (size_t i = 1; i < a.length; ++i) {
337         type val = a[i];
338
339         size_t pos = i-1;
340         for (; pos < a.length && cmp(val, a[pos]); --pos)
341             a[pos+1] = a[pos];
342
343         a[pos+1] = val;
344     }
345 }
346
347 void selectionSort(type[] a) {
348     if (a.length) for (size_t i = 0; i < a.length - 1; ++i) {
349         type min = a[i];
350         size_t m = a.length;
351
352         foreach (j, n; a[i+1..$])
353         if (cmp(n, min)) {
354             min = n;
355             m = j;
356         }
357
358         if (m < a.length) {
359             a[i+m+1] = a[i];
360             a[i] = min;
361         }
362     }
363 }
364
365 void combSort11(type[] a) {
366     // empirically found to be good here: http://world.std.com/~jdveale/combsort.htm
367     // 1 / (1 - 1 / (e ^ phi)), phi being golden ratio = (sqrt(5) + 1)/2
368     const real SHRINK_FACTOR = 1.2473309501039786540366528676643474234433714124826;
369
370     bool swapped = void;
371     size_t gap = a.length;
372
373     if (gap) do {
374         gap = cast(size_t)(cast(real) gap / SHRINK_FACTOR);
375
376         switch (gap) {
377             case     0:  gap = 1; break;
378             case 9, 10: gap = 11; break;
379             default: break;
380         }
381
382         swapped = false;
383
384         for (size_t i = 0; i < a.length - gap; ++i) {
385             size_t j = i + gap;
386
387             if (cmp(a[j], a[i])) {
388                 swap(a[i], a[j]);
389                 swapped = true;
390             }
391         }
392
393     } while (swapped || gap > 1);
394 }
395
396 void shellSort(type[] a) {
397
398     // magic numbers from http://www.research.att.com/~njas/sequences/A102549
399     foreach (h; [701,301,132,57,23,10,4,1])
400     for (size_t i = h; i < a.length; ++i) {
401         type v = a[i];
402         size_t j = i;
403
404         //for (; j >= h && cmp(v, a[j-h]); j -= h)
405         //  a[j] = a[j-h];
406         // measurably faster code below
407
408         do {
409             auto jh = j-h;
410
411             if (!cmp(v, a[jh]))
412                 break;
413
414             a[j] = a[jh];
415             j = jh;
416         } while (j >= h);
417
418         a[j] = v;
419     }
420 }
421
422 void heapSort(type[] a) {
423     if (a.length <= 1)
424         return;
425
426     auto n = a.length;
427     auto i = n / 2;
428
429     for (;;) {
430         type t = void;
431
432         if (i > 0)
433             t = a[--i];
434         else {
435             if (--n == 0)
436                 return;
437
438             t = a[n];
439             a[n] = a[0];
440         }
441
442         auto parent = i;
443         auto child  = i*2 + 1;
444
445         while (child < n) {
446             if (child + 1 < n && cmp(a[child], a[child+1]))
447                 ++child;
448
449             if (cmp(t, a[child])) {
450                 a[parent] = a[child];
451                 parent = child;
452                 child = parent*2 + 1;
453             } else
454                 break;
455         }
456
457         a[parent] = t;
458     }
459 }
460
461 void smoothSort(type[] a) {
462
463     if (a.length <= 1)
464         return;
465
466     static void up(inout size_t b, inout size_t c) {
467         auto
468         t = b + c + 1;
469         c = b;
470         b = t;
471     }
472
473     static void down(inout size_t b, inout size_t c) {
474         auto
475         t = b - c - 1;
476         b = c;
477         c = t;
478     }
479
480     size_t
481         r = 0,
482         p = 1,
483         b = 1,
484         c = 1,
485         r1 = 0,
486         b1 = void,
487         c1 = void;
488
489     void sift() {
490         while (b1 >= 3) {
491             auto r2 = r1 - b1 + c1;
492             auto r3 = r1 - 1;
493
494             if (cmp(a[r2], a[r3])) {
495                 r2 = r3;
496                 down(b1, c1);
497             }
498
499             if (cmp(a[r1], a[r2])) {
500                 swap(a[r1], a[r2]);
501                 r1 = r2;
502                 down(b1, c1);
503             } else
504                 break;
505         }
506     }
507
508     void trinkle() {
509         auto p1 = p;
510
511         while (p1 > 0) {
512             while (p1 % 2 == 0) {
513                 p1 /= 2;
514                 up(b1, c1);
515             }
516             auto r3 = r1 - b1;
517
518             if (p1 == 1 || cmp(a[r3], a[r1]))
519                 break;
520             else {
521                 --p1;
522                 if (b1 == 1) {
523                     swap(a[r1], a[r3]);
524                     r1 = r3;
525                 } else if (b1 >= 3) {
526                     auto r2 = r1 - b1 + c1;
527                     auto r4 = r1 - 1;
528
529                     if (cmp(a[r2], a[r4])) {
530                         r2 = r4;
531                         down(b1, c1);
532                         p1 *= 2;
533                     }
534
535                     if (cmp(a[r3], a[r2])) {
536                         swap(a[r1], a[r2]);
537                         r1 = r2;
538                         down(b1, c1);
539                         break;
540                     } else {
541                         swap(a[r1], a[r3]);
542                         r1 = r3;
543                     }
544                 }
545             }
546         }
547         sift();
548     }
549
550     void semitrinkle() {
551         r1 = r - c;
552         if (cmp(a[r], a[r1])) {
553             swap(a[r], a[r1]);
554             b1 = b;
555             c1 = c;
556             trinkle();
557         }
558     }
559
560     size_t q = 1;
561     for (; q < a.length; ++q) {
562         if (p % 8 == 3) {
563             r1 = r;
564             b1 = b;
565             c1 = c;
566
567             sift();
568             p = (p+1) >>> 2;
569             up(b, c);
570             up(b, c);
571
572         } else if (p % 4 == 1) {
573             r1 = r;
574             b1 = b;
575             c1 = c;
576
577             if (q + c < a.length)
578                 sift();
579             else
580                 trinkle();
581
582             do {
583                 down(b, c);
584                 p *= 2;
585             } while (b > 1);
586
587             ++p;
588         }
589
590         ++r;
591     }
592
593     r1 = r;
594     b1 = <