/
Benchmark.pm6
151 lines (133 loc) · 4.38 KB
/
Benchmark.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
unit module Benchmark;
my sub time_it (Int $count where { $_ > 0 }, Code $code) {
my $start-time = time;
for 1..$count { $code.(); }
my $end-time = time;
my $difference = $end-time - $start-time;
my $average = $difference / $count;
return ($start-time, $end-time, $difference, $average);
}
multi sub timethis (Int $count, Str $code) is export {
my $routine = { EVAL $code };
return time_it($count, $routine);
}
multi sub timethis (Int $count, Code $code) is export {
return time_it($count, $code);
}
sub timethese (Int $count, %h) is export {
my %results;
for %h.kv -> $k, $sub {
%results{$k} = timethis($count, $sub);
}
return %results;
}
use NativeCall;
# Only works on systems where clock_t == long int
constant clock_t = int64;
class tms is repr('CStruct') {
has clock_t $.user-time;
has clock_t $.system-time;
has clock_t $.children-user-time;
has clock_t $.children-system-cstime;
}
class Timing {
has $.time = time;
has tms $.times = times;
}
my sub _times(tms) returns clock_t
is symbol('times')
is native {*}
sub times() {
my tms $buf .= new;
_times($buf);
return $buf;
}
sub timediff(tms $a, tms $b) returns $tms
{
return tms.new(
:user-time($a.user-time - $b.user-time),
:system-time($a.system-time - $b.system-time),
:children-user-time($a.children-user-time - $b.children-user-time),
:children-system-time($a.children-system-time - $b.children-system-time),
);
}
sub count-it(Int $tmax, &code)
{
my ($n, $tc);
# First find the minimum $n that gives a significant timing.
my $zeros = 0;
loop ($n = 1; ; $n *= 2 ) {
my $t0 = times;
my $td = timeit($n, $code);
my $t1 = times;
$tc = $td.user-time + $td.system-time;
if ( $tc <= 0 and $n > 1024 ) {
my $d = timediff($t1, $t0);
# note that $d is the total CPU time taken to call timeit(),
# while $tc is is difference in CPU secs between the empty run
# and the code run. If the code is trivial, its possible
# for $d to get large while $tc is still zero (or slightly
# negative). Bail out once timeit() starts taking more than a
# few seconds without noticeable difference.
if ($d.user-time + $d.system-time > 8
|| ++$zeros > 16)
{
die "Timing is consistently zero in estimation loop, cannot benchmark. N=$n\n";
}
} else {
$zeros = 0;
}
last if $tc > 0.1;
}
my $nmin = $n;
# Get $n high enough that we can guess the final $n with some accuracy.
my $tpra = 0.1 * $tmax; # Target/time practice.
while $tc < $tpra {
# The 5% fudge is to keep us from iterating again all
# that often (this speeds overall responsiveness when $tmax is big
# and we guess a little low). This does not noticeably affect
# accuracy since we're not counting these times.
$n = ( $tpra * 1.05 * $n / $tc ).Int; # Linear approximation.
my $td = timeit($n, $code);
my $new_tc = $td.user-time + $td.system-time;
# Make sure we are making progress.
$tc = $new_tc > 1.2 * $tc ? $new_tc : 1.2 * $tc;
}
# Now, do the 'for real' timing(s), repeating until we exceed
# the max.
my $ntot = 0;
my $rtot = 0;
my $utot = 0.0;
my $stot = 0.0;
my $cutot = 0.0;
my $cstot = 0.0;
my $ttot = 0.0;
# The 5% fudge is because $n is often a few % low even for routines
# with stable times and avoiding extra timeit()s is nice for
# accuracy's sake.
$n = ( $n * ( 1.05 * $tmax / $tc ) ).Int;
$zeros=0;
loop {
my $td = timeit($n, $code);
$ntot += $n;
$rtot += $td->[0];
$utot += $td->[1];
$stot += $td->[2];
$cutot += $td->[3];
$cstot += $td->[4];
$ttot = $utot + $stot;
last if $ttot >= $tmax;
if ( $ttot <= 0 ) {
++$zeros > 16
and die "Timing is consistently zero, cannot benchmark. N=$n\n";
} else {
$zeros = 0;
}
$ttot = 0.01 if $ttot < 0.01;
my $r = $tmax / $ttot - 1; # Linear approximation.
$n = int( $r * $ntot );
$n = $nmin if $n < $nmin;
}
return bless [ $rtot, $utot, $stot, $cutot, $cstot, $ntot ];
}
}