| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Dumbbench; |
|
2
|
3
|
|
|
3
|
|
112568
|
use strict; |
|
|
3
|
|
|
|
|
12
|
|
|
|
3
|
|
|
|
|
71
|
|
|
3
|
3
|
|
|
3
|
|
11
|
use warnings; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
60
|
|
|
4
|
3
|
|
|
3
|
|
10
|
use Carp (); |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
36
|
|
|
5
|
3
|
|
|
3
|
|
1194
|
use Time::HiRes (); |
|
|
3
|
|
|
|
|
3455
|
|
|
|
3
|
|
|
|
|
184
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.503'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
require Dumbbench::Result; |
|
10
|
|
|
|
|
|
|
require Dumbbench::Stats; |
|
11
|
|
|
|
|
|
|
require Dumbbench::Instance; |
|
12
|
|
|
|
|
|
|
|
|
13
|
3
|
|
|
3
|
|
1216
|
use Params::Util '_INSTANCE'; |
|
|
3
|
|
|
|
|
15229
|
|
|
|
3
|
|
|
|
|
219
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use Class::XSAccessor { |
|
16
|
3
|
|
|
|
|
28
|
getters => [qw( |
|
17
|
|
|
|
|
|
|
target_rel_precision |
|
18
|
|
|
|
|
|
|
target_abs_precision |
|
19
|
|
|
|
|
|
|
initial_runs |
|
20
|
|
|
|
|
|
|
max_iterations |
|
21
|
|
|
|
|
|
|
variability_measure |
|
22
|
|
|
|
|
|
|
started |
|
23
|
|
|
|
|
|
|
outlier_rejection |
|
24
|
|
|
|
|
|
|
subtract_dry_run |
|
25
|
|
|
|
|
|
|
)], |
|
26
|
|
|
|
|
|
|
accessors => [qw(verbosity)], |
|
27
|
3
|
|
|
3
|
|
1283
|
}; |
|
|
3
|
|
|
|
|
5478
|
|
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub new { |
|
31
|
3
|
|
|
3
|
1
|
6
|
my $proto = shift; |
|
32
|
3
|
|
33
|
|
|
16
|
my $class = ref($proto)||$proto; |
|
33
|
3
|
|
|
|
|
5
|
my $self; |
|
34
|
3
|
50
|
|
|
|
11
|
if (not ref($proto)) { |
|
35
|
3
|
|
|
|
|
32
|
$self = bless { |
|
36
|
|
|
|
|
|
|
verbosity => 0, |
|
37
|
|
|
|
|
|
|
target_rel_precision => 0.05, |
|
38
|
|
|
|
|
|
|
target_abs_precision => 0, |
|
39
|
|
|
|
|
|
|
initial_runs => 20, |
|
40
|
|
|
|
|
|
|
max_iterations => 10000, |
|
41
|
|
|
|
|
|
|
variability_measure => 'mad', |
|
42
|
|
|
|
|
|
|
instances => [], |
|
43
|
|
|
|
|
|
|
started => 0, |
|
44
|
|
|
|
|
|
|
outlier_rejection => 3, |
|
45
|
|
|
|
|
|
|
subtract_dry_run => 1, |
|
46
|
|
|
|
|
|
|
@_, |
|
47
|
|
|
|
|
|
|
} => $class; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
else { |
|
50
|
0
|
|
|
|
|
0
|
$self = bless {%$proto, @_} => $class; |
|
51
|
0
|
|
|
|
|
0
|
my @inst = $self->instances; |
|
52
|
0
|
|
|
|
|
0
|
$self->{instances} = []; |
|
53
|
0
|
|
|
|
|
0
|
foreach my $instance (@inst) { |
|
54
|
0
|
|
|
|
|
0
|
push @{$self->{instances}}, $instance->new; |
|
|
0
|
|
|
|
|
0
|
|
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
3
|
50
|
33
|
|
|
36
|
if ($self->target_abs_precision <= 0 and $self->target_rel_precision <= 0) { |
|
59
|
0
|
|
|
|
|
0
|
Carp::croak("Need either target_rel_precision or target_abs_precision > 0"); |
|
60
|
|
|
|
|
|
|
} |
|
61
|
3
|
50
|
|
|
|
12
|
if ($self->initial_runs < 6) { |
|
62
|
3
|
|
|
|
|
963
|
Carp::carp("Number of initial runs is very small (<6). Precision will be off."); |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
|
|
65
|
3
|
|
|
|
|
102
|
return $self; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub add_instances { |
|
69
|
4
|
|
|
4
|
1
|
5
|
my $self = shift; |
|
70
|
|
|
|
|
|
|
|
|
71
|
4
|
50
|
|
|
|
59
|
if ($self->started) { |
|
72
|
0
|
|
|
|
|
0
|
Carp::croak("Can't add instances after the benchmark has been started"); |
|
73
|
|
|
|
|
|
|
} |
|
74
|
4
|
|
|
|
|
10
|
foreach my $instance (@_) { |
|
75
|
4
|
50
|
|
|
|
47
|
if (not _INSTANCE($instance, 'Dumbbench::Instance')) { |
|
76
|
0
|
|
|
|
|
0
|
Carp::croak("Argument to add_instances is not a Dumbbench::Instance"); |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
} |
|
79
|
4
|
|
|
|
|
9
|
push @{$self->{instances}}, @_; |
|
|
4
|
|
|
|
|
11
|
|
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub instances { |
|
83
|
10
|
|
|
10
|
1
|
18
|
my $self = shift; |
|
84
|
10
|
|
|
|
|
13
|
return @{$self->{instances}}; |
|
|
10
|
|
|
|
|
137
|
|
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub run { |
|
88
|
3
|
|
|
3
|
1
|
6
|
my $self = shift; |
|
89
|
3
|
50
|
|
|
|
8
|
Carp::croak("Can't re-run same benchmark instance") if $self->started; |
|
90
|
3
|
50
|
|
|
|
16
|
$self->dry_run_timings if $self->subtract_dry_run; |
|
91
|
3
|
|
|
|
|
29
|
$self->run_timings; |
|
92
|
|
|
|
|
|
|
} |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub run_timings { |
|
95
|
3
|
|
|
3
|
0
|
13
|
my $self = shift; |
|
96
|
3
|
|
|
|
|
5
|
$self->{started} = 1; |
|
97
|
3
|
|
|
|
|
12
|
foreach my $instance ($self->instances) { |
|
98
|
4
|
50
|
|
|
|
22
|
next if $instance->result; |
|
99
|
4
|
|
|
|
|
9
|
$self->_run($instance); |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub dry_run_timings { |
|
104
|
3
|
|
|
3
|
0
|
6
|
my $self = shift; |
|
105
|
3
|
|
|
|
|
6
|
$self->{started} = 1; |
|
106
|
|
|
|
|
|
|
|
|
107
|
3
|
|
|
|
|
12
|
foreach my $instance ($self->instances) { |
|
108
|
4
|
50
|
|
|
|
20
|
next if $instance->dry_result; |
|
109
|
4
|
|
|
|
|
16
|
$self->_run($instance, 'dry'); |
|
110
|
|
|
|
|
|
|
} |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
sub _run { |
|
114
|
8
|
|
|
8
|
|
14
|
my $self = shift; |
|
115
|
8
|
|
|
|
|
13
|
my $instance = shift; |
|
116
|
8
|
|
|
|
|
13
|
my $dry = shift; |
|
117
|
|
|
|
|
|
|
|
|
118
|
8
|
|
|
|
|
37
|
my $name = $instance->_name_prefix; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# for overriding in case of dry-run mode |
|
121
|
8
|
|
50
|
|
|
38
|
my $V = $self->verbosity || 0; |
|
122
|
8
|
|
|
|
|
20
|
my $initial_timings = $self->initial_runs; |
|
123
|
8
|
|
|
|
|
18
|
my $abs_precision = $self->target_abs_precision; |
|
124
|
8
|
|
|
|
|
18
|
my $rel_precision = $self->target_rel_precision; |
|
125
|
8
|
|
|
|
|
15
|
my $max_iterations = $self->max_iterations; |
|
126
|
|
|
|
|
|
|
|
|
127
|
8
|
100
|
|
|
|
17
|
if ($dry) { |
|
128
|
4
|
50
|
|
|
|
6
|
$V--; $V = 0 if $V < 0; |
|
|
4
|
|
|
|
|
9
|
|
|
129
|
4
|
|
|
|
|
8
|
$initial_timings *= 5; |
|
130
|
4
|
|
|
|
|
5
|
$abs_precision = 0; |
|
131
|
4
|
|
|
|
|
9
|
$rel_precision /= 2; |
|
132
|
4
|
|
|
|
|
4
|
$max_iterations *= 10; |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
|
|
135
|
8
|
50
|
|
|
|
19
|
print "${name}Running initial timing for warming up the cache...\n" if $V; |
|
136
|
8
|
100
|
|
|
|
16
|
if ($dry) { |
|
137
|
|
|
|
|
|
|
# be generous, this is fast |
|
138
|
4
|
|
|
|
|
23
|
$instance->single_dry_run() for 1..3; |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
else { |
|
141
|
4
|
|
|
|
|
14
|
$instance->single_run(); |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
8
|
|
|
|
|
13
|
my @timings; |
|
145
|
8
|
50
|
|
|
|
19
|
print "${name}Running $initial_timings initial timings...\n" if $V; |
|
146
|
8
|
|
|
|
|
15
|
foreach (1..$initial_timings) { |
|
147
|
24
|
50
|
|
|
|
40
|
print "${name}Running timing $_...\n" if $V > 1; |
|
148
|
24
|
100
|
|
|
|
48
|
push @timings, ($dry ? $instance->single_dry_run() : $instance->single_run()); |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
8
|
50
|
|
|
|
18
|
print "${name}Iterating until target precision reached...\n" if $V; |
|
152
|
|
|
|
|
|
|
|
|
153
|
8
|
|
|
|
|
54
|
my $stats = Dumbbench::Stats->new(data => \@timings); |
|
154
|
8
|
|
|
|
|
14
|
my $sigma; |
|
155
|
|
|
|
|
|
|
my $mean; |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
#My mental model for the distribution was Gauss+outliers. |
|
158
|
|
|
|
|
|
|
#If my expectation is correct, the following algorithm should produce a reasonable EV +/- uncertainty: |
|
159
|
|
|
|
|
|
|
#1) Calc. median of the whole distribution. |
|
160
|
|
|
|
|
|
|
#2) Calculate the median-absolute deviation from the whole distribution (MAD, see wikipedia). It needs rescaling to become a measure of variability that is robust against outliers. |
|
161
|
|
|
|
|
|
|
#(The MAD will be our initial guess for a "sigma") |
|
162
|
|
|
|
|
|
|
#3) Reject the samples that are outside $median +/- $n*$MAD. |
|
163
|
|
|
|
|
|
|
#I was expecting several high outliers but few lows. An ordinary truncated mean or the like would be unsuitable for removing the outliers in such a case since you'd get a significant upward bias of your EV. |
|
164
|
|
|
|
|
|
|
#By using the median as the initial guess, we keep the initial bias to a minimum. The MAD will be similarly unaffected by outliers AND the asymmetry. |
|
165
|
|
|
|
|
|
|
#Thus cutting the tails won't blow up the bias too strongly (hopefully). |
|
166
|
|
|
|
|
|
|
#4) Calculate mean & MAD/sqrt($n) of the remaining distribution. These are our EV and uncertainty on the mean. |
|
167
|
|
|
|
|
|
|
|
|
168
|
8
|
|
|
|
|
12
|
my $n_good = 0; |
|
169
|
8
|
|
|
|
|
22
|
my $variability_measure = $self->variability_measure; |
|
170
|
8
|
|
|
|
|
12
|
while (1) { |
|
171
|
12
|
|
|
|
|
48
|
my ($good, $outliers) = $stats->filter_outliers( |
|
172
|
|
|
|
|
|
|
variability_measure => $variability_measure, |
|
173
|
|
|
|
|
|
|
nsigma_outliers => $self->outlier_rejection, |
|
174
|
|
|
|
|
|
|
); |
|
175
|
|
|
|
|
|
|
|
|
176
|
12
|
|
|
|
|
19
|
$n_good = @$good; |
|
177
|
|
|
|
|
|
|
|
|
178
|
12
|
50
|
33
|
|
|
27
|
if (not $n_good and @timings >= $max_iterations) { |
|
179
|
0
|
|
|
|
|
0
|
$mean = 0; $sigma = 0; |
|
|
0
|
|
|
|
|
0
|
|
|
180
|
0
|
|
|
|
|
0
|
last; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
12
|
50
|
|
|
|
34
|
if ($n_good) { |
|
184
|
12
|
|
|
|
|
25
|
my $new_stats = Dumbbench::Stats->new(data => $good); |
|
185
|
12
|
|
|
|
|
27
|
$sigma = $new_stats->$variability_measure() / sqrt($n_good); |
|
186
|
12
|
|
|
|
|
87
|
$mean = $new_stats->mean(); |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# stop condition |
|
189
|
12
|
|
|
|
|
14
|
my $need_iter = 0; |
|
190
|
12
|
50
|
|
|
|
28
|
if ($rel_precision > 0) { |
|
191
|
12
|
|
|
|
|
19
|
my $rel = $sigma/$mean; |
|
192
|
12
|
50
|
|
|
|
18
|
print "${name}Reached relative precision $rel (neeed $rel_precision).\n" if $V > 1; |
|
193
|
12
|
50
|
|
|
|
23
|
$need_iter++ if $rel > $rel_precision; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
12
|
50
|
|
|
|
20
|
if ($abs_precision > 0) { |
|
196
|
0
|
0
|
|
|
|
0
|
print "${name}Reached absolute precision $sigma (neeed $abs_precision).\n" if $V > 1; |
|
197
|
0
|
0
|
|
|
|
0
|
$need_iter++ if $sigma > $abs_precision; |
|
198
|
|
|
|
|
|
|
} |
|
199
|
12
|
100
|
|
|
|
18
|
if ($n_good < $initial_timings) { |
|
200
|
4
|
|
|
|
|
6
|
$need_iter++; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
12
|
100
|
66
|
|
|
40
|
last if not $need_iter or @timings >= $max_iterations; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# progressively run more new timings in one go. Otherwise, |
|
206
|
|
|
|
|
|
|
# we start to stall on the O(n*log(n)) complexity of the median. |
|
207
|
4
|
|
|
|
|
21
|
my $n = List::Util::min( $max_iterations - @timings, List::Util::max(1, @timings*0.05) ); |
|
208
|
4
|
50
|
|
|
|
27
|
push @timings, ($dry ? $instance->single_dry_run() : $instance->single_run()) for 1..$n; |
|
209
|
|
|
|
|
|
|
} # end while more data required |
|
210
|
|
|
|
|
|
|
|
|
211
|
8
|
50
|
33
|
|
|
21
|
if (@timings >= $max_iterations and not $dry) { |
|
212
|
0
|
|
|
|
|
0
|
print "${name}Reached maximum number of iterations. Stopping. Precision not reached.\n"; |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# rescale sigma |
|
216
|
|
|
|
|
|
|
# This is necessary since by cutting everything outside of n-sigma, |
|
217
|
|
|
|
|
|
|
# we artificially reduce the variability of the main distribution. |
|
218
|
8
|
50
|
|
|
|
23
|
if ($self->outlier_rejection) { |
|
219
|
|
|
|
|
|
|
# TODO implement |
|
220
|
|
|
|
|
|
|
} |
|
221
|
|
|
|
|
|
|
|
|
222
|
8
|
|
|
|
|
46
|
my $result = Dumbbench::Result->new( |
|
223
|
|
|
|
|
|
|
timing => $mean, |
|
224
|
|
|
|
|
|
|
uncertainty => $sigma, |
|
225
|
|
|
|
|
|
|
nsamples => $n_good, |
|
226
|
|
|
|
|
|
|
); |
|
227
|
|
|
|
|
|
|
|
|
228
|
8
|
100
|
|
|
|
20
|
if ($dry) { |
|
229
|
4
|
|
|
|
|
12
|
$instance->{dry_timings} = \@timings; |
|
230
|
4
|
|
|
|
|
23
|
$instance->dry_result($result); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
else { |
|
233
|
4
|
|
|
|
|
8
|
$instance->{timings} = \@timings; |
|
234
|
4
|
50
|
33
|
|
|
37
|
$result -= $instance->dry_result |
|
235
|
|
|
|
|
|
|
if defined $instance->dry_result and $self->subtract_dry_run; |
|
236
|
4
|
|
|
|
|
507
|
$instance->result($result); |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub report { |
|
241
|
0
|
|
|
0
|
1
|
|
my ( $self, $raw, $options ) = @_; |
|
242
|
0
|
|
0
|
|
|
|
$options ||= {}; |
|
243
|
0
|
0
|
|
|
|
|
Carp::carp( "The second option to report was not a hash ref" ) |
|
244
|
|
|
|
|
|
|
unless ref $options eq ref {}; |
|
245
|
|
|
|
|
|
|
|
|
246
|
0
|
|
|
|
|
|
foreach my $instance ($self->instances) { |
|
247
|
0
|
|
|
|
|
|
my $result = $instance->result; |
|
248
|
0
|
0
|
|
|
|
|
my $result_str = ($options->{float}) ? unscientific_notation($result) : "$result"; |
|
249
|
|
|
|
|
|
|
|
|
250
|
0
|
0
|
|
|
|
|
if (not $raw) { |
|
251
|
0
|
|
|
|
|
|
my $mean = $result->raw_number; |
|
252
|
0
|
|
|
|
|
|
my $sigma = $result->raw_error->[0]; |
|
253
|
0
|
|
|
|
|
|
my $name = $instance->_name_prefix; |
|
254
|
|
|
|
|
|
|
printf( |
|
255
|
|
|
|
|
|
|
"%sRan %u iterations (%u outliers).\n", |
|
256
|
|
|
|
|
|
|
$name, |
|
257
|
0
|
|
|
|
|
|
scalar(@{$instance->timings}), |
|
258
|
0
|
|
|
|
|
|
scalar(@{$instance->timings})-$result->nsamples |
|
|
0
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
); |
|
260
|
0
|
|
|
|
|
|
printf( |
|
261
|
|
|
|
|
|
|
"%sRounded run time per iteration (seconds): %s (%.1f%%)\n", |
|
262
|
|
|
|
|
|
|
$name, |
|
263
|
|
|
|
|
|
|
$result_str, |
|
264
|
|
|
|
|
|
|
$sigma/$mean*100 |
|
265
|
|
|
|
|
|
|
); |
|
266
|
0
|
0
|
|
|
|
|
if ($self->verbosity) { |
|
267
|
0
|
|
|
|
|
|
printf("%sRaw: $mean +/- $sigma\n", $name); |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
else { |
|
271
|
0
|
|
|
|
|
|
print $result_str, "\n"; |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
} |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub box_plot { |
|
277
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
278
|
0
|
|
|
|
|
|
require Dumbbench::BoxPlot; |
|
279
|
|
|
|
|
|
|
|
|
280
|
0
|
|
|
|
|
|
return Dumbbench::BoxPlot->new($self); |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub unscientific_notation { |
|
284
|
0
|
|
|
0
|
0
|
|
sprintf( "%f %s %f", split( / /, $_[0] ) ); |
|
285
|
|
|
|
|
|
|
} |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
1; |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
__END__ |