benchmark-ips 2.3.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/History.md +228 -0
- data/LICENSE +20 -0
- data/README.md +129 -29
- data/examples/advanced.rb +20 -0
- data/examples/hold.rb +41 -0
- data/examples/save.rb +50 -0
- data/examples/simple.rb +47 -0
- data/lib/benchmark/compare.rb +59 -23
- data/lib/benchmark/ips/job/entry.rb +95 -0
- data/lib/benchmark/ips/job/noop_report.rb +27 -0
- data/lib/benchmark/ips/job/stdout_report.rb +64 -0
- data/lib/benchmark/ips/job.rb +211 -150
- data/lib/benchmark/ips/noop_suite.rb +25 -0
- data/lib/benchmark/ips/report.rb +53 -30
- data/lib/benchmark/ips/share.rb +50 -0
- data/lib/benchmark/ips/stats/bootstrap.rb +58 -0
- data/lib/benchmark/ips/stats/sd.rb +45 -0
- data/lib/benchmark/ips/stats/stats_metric.rb +21 -0
- data/lib/benchmark/ips.rb +91 -24
- data/lib/benchmark/timing.rb +39 -16
- metadata +24 -31
- data/.autotest +0 -23
- data/.gemtest +0 -0
- data/History.txt +0 -87
- data/Manifest.txt +0 -11
- data/Rakefile +0 -26
- data/test/test_benchmark_ips.rb +0 -161
data/lib/benchmark/ips/job.rb
CHANGED
@@ -5,95 +5,11 @@ module Benchmark
|
|
5
5
|
# Microseconds per 100 millisecond.
|
6
6
|
MICROSECONDS_PER_100MS = 100_000
|
7
7
|
# Microseconds per second.
|
8
|
-
MICROSECONDS_PER_SECOND =
|
8
|
+
MICROSECONDS_PER_SECOND = Timing::MICROSECONDS_PER_SECOND
|
9
9
|
# The percentage of the expected runtime to allow
|
10
10
|
# before reporting a weird runtime
|
11
11
|
MAX_TIME_SKEW = 0.05
|
12
|
-
|
13
|
-
# Entries in Benchmark Jobs.
|
14
|
-
class Entry
|
15
|
-
# Instantiate the Benchmark::IPS::Job::Entry.
|
16
|
-
# @param label [#to_s] Label of Benchmarked code.
|
17
|
-
# @param action [String, Proc] Code to be benchmarked.
|
18
|
-
# @raise [ArgumentError] Raises when action is not String or not responding to +call+.
|
19
|
-
def initialize(label, action)
|
20
|
-
@label = label
|
21
|
-
|
22
|
-
if action.kind_of? String
|
23
|
-
compile action
|
24
|
-
@action = self
|
25
|
-
@as_action = true
|
26
|
-
else
|
27
|
-
unless action.respond_to? :call
|
28
|
-
raise ArgumentError, "invalid action, must respond to #call"
|
29
|
-
end
|
30
|
-
|
31
|
-
@action = action
|
32
|
-
|
33
|
-
if action.respond_to? :arity and action.arity > 0
|
34
|
-
@call_loop = true
|
35
|
-
else
|
36
|
-
@call_loop = false
|
37
|
-
end
|
38
|
-
|
39
|
-
@as_action = false
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# The label of benchmarking action.
|
44
|
-
# @return [#to_s] Label of action.
|
45
|
-
attr_reader :label
|
46
|
-
|
47
|
-
# The benchmarking action.
|
48
|
-
# @return [String, Proc] Code to be called, could be String / Proc.
|
49
|
-
attr_reader :action
|
50
|
-
|
51
|
-
# Add padding to label's right if label's length < 20,
|
52
|
-
# Otherwise add a new line and 20 whitespaces.
|
53
|
-
# @return [String] Right justified label.
|
54
|
-
def label_rjust
|
55
|
-
label = @label.to_s
|
56
|
-
if label.size > 20
|
57
|
-
"#{label}\n#{' ' * 20}"
|
58
|
-
else
|
59
|
-
label.rjust(20)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Call action by given times, return if +@call_loop+ is present.
|
64
|
-
# @param times [Integer] Times to call +@action+.
|
65
|
-
# @return [Integer] Number of times the +@action+ has been called.
|
66
|
-
def call_times(times)
|
67
|
-
return @action.call(times) if @call_loop
|
68
|
-
|
69
|
-
act = @action
|
70
|
-
|
71
|
-
i = 0
|
72
|
-
while i < times
|
73
|
-
act.call
|
74
|
-
i += 1
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# Compile code into +call_times+ method.
|
79
|
-
# @param str [String] Code to be compiled.
|
80
|
-
# @return [Symbol] :call_times.
|
81
|
-
def compile(str)
|
82
|
-
m = (class << self; self; end)
|
83
|
-
code = <<-CODE
|
84
|
-
def call_times(__total);
|
85
|
-
__i = 0
|
86
|
-
while __i < __total
|
87
|
-
#{str};
|
88
|
-
__i += 1
|
89
|
-
end
|
90
|
-
end
|
91
|
-
CODE
|
92
|
-
m.class_eval code
|
93
|
-
end
|
94
|
-
end # End of Entry
|
95
|
-
|
96
|
-
# class Job
|
12
|
+
POW_2_30 = 1 << 30
|
97
13
|
|
98
14
|
# Two-element arrays, consisting of label and block pairs.
|
99
15
|
# @return [Array<Entry>] list of entries
|
@@ -103,6 +19,10 @@ module Benchmark
|
|
103
19
|
# @return [Boolean] true if needs to run compare.
|
104
20
|
attr_reader :compare
|
105
21
|
|
22
|
+
# Determining whether to hold results between Ruby invocations
|
23
|
+
# @return [Boolean]
|
24
|
+
attr_accessor :hold
|
25
|
+
|
106
26
|
# Report object containing information about the run.
|
107
27
|
# @return [Report] the report object.
|
108
28
|
attr_reader :full_report
|
@@ -119,31 +39,76 @@ module Benchmark
|
|
119
39
|
# @return [Integer]
|
120
40
|
attr_accessor :time
|
121
41
|
|
42
|
+
# Warmup and calculation iterations.
|
43
|
+
# @return [Integer]
|
44
|
+
attr_accessor :iterations
|
45
|
+
|
46
|
+
# Statistics model.
|
47
|
+
# @return [Object]
|
48
|
+
attr_accessor :stats
|
49
|
+
|
50
|
+
# Confidence.
|
51
|
+
# @return [Integer]
|
52
|
+
attr_accessor :confidence
|
53
|
+
|
54
|
+
# Silence output
|
55
|
+
# @return [Boolean]
|
56
|
+
attr_reader :quiet
|
57
|
+
|
58
|
+
# Suite
|
59
|
+
# @return [Benchmark::IPS::NoopSuite]
|
60
|
+
attr_reader :suite
|
61
|
+
|
122
62
|
# Instantiate the Benchmark::IPS::Job.
|
123
|
-
# @option opts [Benchmark::Suite] (nil) :suite Specify Benchmark::Suite.
|
124
|
-
# @option opts [Boolean] (false) :quiet Suppress the printing of information.
|
125
63
|
def initialize opts={}
|
126
|
-
@suite = opts[:suite] || nil
|
127
|
-
@quiet = opts[:quiet] || false
|
128
64
|
@list = []
|
129
|
-
@
|
65
|
+
@run_single = false
|
130
66
|
@json_path = false
|
67
|
+
@compare = false
|
68
|
+
@compare_order = :fastest
|
69
|
+
@held_path = nil
|
70
|
+
@held_results = nil
|
131
71
|
|
132
|
-
@timing =
|
72
|
+
@timing = Hash.new 1 # default to 1 in case warmup isn't run
|
133
73
|
@full_report = Report.new
|
134
74
|
|
135
75
|
# Default warmup and calculation time in seconds.
|
136
76
|
@warmup = 2
|
137
77
|
@time = 5
|
78
|
+
@iterations = 1
|
79
|
+
|
80
|
+
# Default statistical model
|
81
|
+
@stats = :sd
|
82
|
+
@confidence = 95
|
83
|
+
|
84
|
+
self.quiet = false
|
138
85
|
end
|
139
86
|
|
140
87
|
# Job configuration options, set +@warmup+ and +@time+.
|
141
88
|
# @option opts [Integer] :warmup Warmup time.
|
142
89
|
# @option opts [Integer] :time Calculation time.
|
90
|
+
# @option iterations [Integer] :time Warmup and calculation iterations.
|
143
91
|
def config opts
|
144
92
|
@warmup = opts[:warmup] if opts[:warmup]
|
145
93
|
@time = opts[:time] if opts[:time]
|
146
94
|
@suite = opts[:suite] if opts[:suite]
|
95
|
+
@iterations = opts[:iterations] if opts[:iterations]
|
96
|
+
@stats = opts[:stats] if opts[:stats]
|
97
|
+
@confidence = opts[:confidence] if opts[:confidence]
|
98
|
+
self.quiet = opts[:quiet] if opts.key?(:quiet)
|
99
|
+
self.suite = opts[:suite]
|
100
|
+
end
|
101
|
+
|
102
|
+
def quiet=(val)
|
103
|
+
@stdout = reporter(quiet: val)
|
104
|
+
end
|
105
|
+
|
106
|
+
def suite=(suite)
|
107
|
+
@suite = suite || Benchmark::IPS::NoopSuite.new
|
108
|
+
end
|
109
|
+
|
110
|
+
def reporter(quiet:)
|
111
|
+
quiet ? NoopReport.new : StdoutReport.new
|
147
112
|
end
|
148
113
|
|
149
114
|
# Return true if job needs to be compared.
|
@@ -152,11 +117,40 @@ module Benchmark
|
|
152
117
|
@compare
|
153
118
|
end
|
154
119
|
|
155
|
-
#
|
156
|
-
def compare!
|
120
|
+
# Run comparison utility.
|
121
|
+
def compare!(order: :fastest)
|
157
122
|
@compare = true
|
123
|
+
@compare_order = order
|
124
|
+
end
|
125
|
+
|
126
|
+
# Return true if results are held while multiple Ruby invocations
|
127
|
+
# @return [Boolean] Need to hold results between multiple Ruby invocations?
|
128
|
+
def hold?
|
129
|
+
!!@held_path
|
130
|
+
end
|
131
|
+
|
132
|
+
# Hold after each iteration.
|
133
|
+
# @param held_path [String] File name to store hold file.
|
134
|
+
def hold!(held_path)
|
135
|
+
@held_path = held_path
|
136
|
+
@run_single = true
|
158
137
|
end
|
159
138
|
|
139
|
+
# Save interim results. Similar to hold, but all reports are run
|
140
|
+
# The report label must change for each invocation.
|
141
|
+
# One way to achieve this is to include the version in the label.
|
142
|
+
# @param held_path [String] File name to store hold file.
|
143
|
+
def save!(held_path)
|
144
|
+
@held_path = held_path
|
145
|
+
@run_single = false
|
146
|
+
end
|
147
|
+
|
148
|
+
# Return true if items are to be run one at a time.
|
149
|
+
# For the traditional hold, this is true
|
150
|
+
# @return [Boolean] Run just a single item?
|
151
|
+
def run_single?
|
152
|
+
@run_single
|
153
|
+
end
|
160
154
|
|
161
155
|
# Return true if job needs to generate json.
|
162
156
|
# @return [Boolean] Need to generate json?
|
@@ -164,15 +158,15 @@ module Benchmark
|
|
164
158
|
!!@json_path
|
165
159
|
end
|
166
160
|
|
167
|
-
#
|
161
|
+
# Generate json to given path, defaults to "data.json".
|
168
162
|
def json!(path="data.json")
|
169
163
|
@json_path = path
|
170
164
|
end
|
171
165
|
|
172
166
|
# Registers the given label and block pair in the job list.
|
173
167
|
# @param label [String] Label of benchmarked code.
|
174
|
-
# @param str [String] Code to be
|
175
|
-
# @param blk [Proc] Code to be
|
168
|
+
# @param str [String] Code to be benchmarked.
|
169
|
+
# @param blk [Proc] Code to be benchmarked.
|
176
170
|
# @raise [ArgumentError] Raises if str and blk are both present.
|
177
171
|
# @raise [ArgumentError] Raises if str and blk are both absent.
|
178
172
|
def item(label="", str=nil, &blk) # :yield:
|
@@ -195,8 +189,7 @@ module Benchmark
|
|
195
189
|
# @return [Integer] Cycles per 100ms.
|
196
190
|
def cycles_per_100ms time_msec, iters
|
197
191
|
cycles = ((MICROSECONDS_PER_100MS / time_msec) * iters).to_i
|
198
|
-
cycles
|
199
|
-
cycles
|
192
|
+
cycles <= 0 ? 1 : cycles
|
200
193
|
end
|
201
194
|
|
202
195
|
# Calculate the time difference of before and after in microseconds.
|
@@ -216,121 +209,189 @@ module Benchmark
|
|
216
209
|
MICROSECONDS_PER_SECOND * (cycles.to_f / time_us.to_f)
|
217
210
|
end
|
218
211
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
212
|
+
def load_held_results
|
213
|
+
return unless @held_path && File.exist?(@held_path) && !File.zero?(@held_path)
|
214
|
+
require "json"
|
215
|
+
@held_results = {}
|
216
|
+
JSON.load(IO.read(@held_path)).each do |result|
|
217
|
+
@held_results[result['item']] = result
|
218
|
+
create_report(result['item'], result['measured_us'], result['iter'],
|
219
|
+
create_stats(result['samples']), result['cycles'])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def save_held_results
|
224
|
+
return unless @held_path
|
225
|
+
require "json"
|
226
|
+
data = full_report.entries.map { |e|
|
227
|
+
{
|
228
|
+
'item' => e.label,
|
229
|
+
'measured_us' => e.microseconds,
|
230
|
+
'iter' => e.iterations,
|
231
|
+
'samples' => e.samples,
|
232
|
+
'cycles' => e.measurement_cycle
|
233
|
+
}
|
234
|
+
}
|
235
|
+
IO.write(@held_path, JSON.generate(data) << "\n")
|
236
|
+
end
|
237
|
+
|
238
|
+
def all_results_have_been_run?
|
239
|
+
@full_report.entries.size == @list.size
|
240
|
+
end
|
241
|
+
|
242
|
+
def clear_held_results
|
243
|
+
File.delete @held_path if File.exist?(@held_path)
|
244
|
+
end
|
223
245
|
|
224
|
-
|
225
|
-
|
246
|
+
def run
|
247
|
+
if @warmup && @warmup != 0 then
|
248
|
+
@stdout.start_warming
|
249
|
+
@iterations.times do
|
250
|
+
run_warmup
|
226
251
|
end
|
252
|
+
end
|
227
253
|
|
228
|
-
|
254
|
+
@stdout.start_running
|
229
255
|
|
230
|
-
|
231
|
-
|
256
|
+
@iterations.times do |n|
|
257
|
+
run_benchmark
|
258
|
+
end
|
232
259
|
|
233
|
-
|
260
|
+
@stdout.footer
|
261
|
+
end
|
234
262
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
263
|
+
# Run warmup.
|
264
|
+
def run_warmup
|
265
|
+
@list.each do |item|
|
266
|
+
next if run_single? && @held_results && @held_results.key?(item.label)
|
239
267
|
|
240
|
-
|
268
|
+
@suite.warming item.label, @warmup
|
269
|
+
@stdout.warming item.label, @warmup
|
241
270
|
|
242
|
-
|
271
|
+
Timing.clean_env
|
243
272
|
|
244
|
-
|
273
|
+
# Run for up to half of the configured warmup time with an increasing
|
274
|
+
# number of cycles to reduce overhead and improve accuracy.
|
275
|
+
# This also avoids running with a constant number of cycles, which a
|
276
|
+
# JIT might speculate on and then have to recompile in #run_benchmark.
|
277
|
+
before = Timing.now
|
278
|
+
target = Timing.add_second before, @warmup / 2.0
|
245
279
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
280
|
+
cycles = 1
|
281
|
+
begin
|
282
|
+
t0 = Timing.now
|
283
|
+
item.call_times cycles
|
284
|
+
t1 = Timing.now
|
285
|
+
warmup_iter = cycles
|
286
|
+
warmup_time_us = Timing.time_us(t0, t1)
|
287
|
+
|
288
|
+
# If the number of cycles would go outside the 32-bit signed integers range
|
289
|
+
# then exit the loop to avoid overflows and start the 100ms warmup runs
|
290
|
+
break if cycles >= POW_2_30
|
291
|
+
cycles *= 2
|
292
|
+
end while Timing.now + warmup_time_us * 2 < target
|
293
|
+
|
294
|
+
cycles = cycles_per_100ms warmup_time_us, warmup_iter
|
295
|
+
@timing[item] = cycles
|
296
|
+
|
297
|
+
# Run for the remaining of warmup in a similar way as #run_benchmark.
|
298
|
+
target = Timing.add_second before, @warmup
|
299
|
+
while Timing.now + MICROSECONDS_PER_100MS < target
|
300
|
+
item.call_times cycles
|
251
301
|
end
|
252
302
|
|
253
|
-
@
|
303
|
+
@stdout.warmup_stats warmup_time_us, @timing[item]
|
304
|
+
@suite.warmup_stats warmup_time_us, @timing[item]
|
305
|
+
|
306
|
+
break if run_single?
|
254
307
|
end
|
255
308
|
end
|
256
309
|
|
257
310
|
# Run calculation.
|
258
|
-
def
|
311
|
+
def run_benchmark
|
259
312
|
@list.each do |item|
|
260
|
-
|
313
|
+
next if run_single? && @held_results && @held_results.key?(item.label)
|
261
314
|
|
262
|
-
|
263
|
-
|
264
|
-
end
|
315
|
+
@suite.running item.label, @time
|
316
|
+
@stdout.running item.label, @time
|
265
317
|
|
266
318
|
Timing.clean_env
|
267
319
|
|
268
320
|
iter = 0
|
269
321
|
|
270
|
-
target = Time.now + @time
|
271
|
-
|
272
322
|
measurements_us = []
|
273
323
|
|
274
324
|
# Running this number of cycles should take around 100ms.
|
275
325
|
cycles = @timing[item]
|
276
326
|
|
277
|
-
|
278
|
-
|
327
|
+
target = Timing.add_second Timing.now, @time
|
328
|
+
|
329
|
+
begin
|
330
|
+
before = Timing.now
|
279
331
|
item.call_times cycles
|
280
|
-
after =
|
332
|
+
after = Timing.now
|
281
333
|
|
282
334
|
# If for some reason the timing said this took no time (O_o)
|
283
335
|
# then ignore the iteration entirely and start another.
|
284
|
-
iter_us = time_us before, after
|
336
|
+
iter_us = Timing.time_us before, after
|
285
337
|
next if iter_us <= 0.0
|
286
338
|
|
287
339
|
iter += cycles
|
288
340
|
|
289
341
|
measurements_us << iter_us
|
290
|
-
end
|
342
|
+
end while Timing.now < target
|
291
343
|
|
292
|
-
final_time =
|
344
|
+
final_time = before
|
293
345
|
|
294
|
-
measured_us = measurements_us.inject(
|
346
|
+
measured_us = measurements_us.inject(:+)
|
295
347
|
|
296
|
-
|
348
|
+
samples = measurements_us.map { |time_us|
|
297
349
|
iterations_per_sec cycles, time_us
|
298
350
|
}
|
299
351
|
|
300
|
-
|
301
|
-
sd_ips = Timing.stddev(all_ips).round
|
302
|
-
|
303
|
-
rep = create_report(item, measured_us, iter, avg_ips, sd_ips, cycles)
|
352
|
+
rep = create_report(item.label, measured_us, iter, create_stats(samples), cycles)
|
304
353
|
|
305
354
|
if (final_time - target).abs >= (@time.to_f * MAX_TIME_SKEW)
|
306
355
|
rep.show_total_time!
|
307
356
|
end
|
308
357
|
|
309
|
-
|
358
|
+
@stdout.add_report rep, caller(1).first
|
359
|
+
@suite.add_report rep, caller(1).first
|
310
360
|
|
311
|
-
|
361
|
+
break if run_single?
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def create_stats(samples)
|
366
|
+
case @stats
|
367
|
+
when :sd
|
368
|
+
Stats::SD.new(samples)
|
369
|
+
when :bootstrap
|
370
|
+
Stats::Bootstrap.new(samples, @confidence)
|
371
|
+
else
|
372
|
+
raise "unknown stats #{@stats}"
|
312
373
|
end
|
313
374
|
end
|
314
375
|
|
315
376
|
# Run comparison of entries in +@full_report+.
|
316
377
|
def run_comparison
|
317
|
-
@full_report.run_comparison
|
378
|
+
@full_report.run_comparison(@compare_order) if compare?
|
318
379
|
end
|
319
380
|
|
320
381
|
# Generate json from +@full_report+.
|
321
382
|
def generate_json
|
322
|
-
@full_report.generate_json @json_path
|
383
|
+
@full_report.generate_json @json_path if json?
|
323
384
|
end
|
324
385
|
|
325
386
|
# Create report by add entry to +@full_report+.
|
326
|
-
# @param
|
387
|
+
# @param label [String] Report item label.
|
327
388
|
# @param measured_us [Integer] Measured time in microsecond.
|
328
389
|
# @param iter [Integer] Iterations.
|
329
|
-
# @param
|
330
|
-
# @param sd_ips [Float] Standard deviation iterations per second.
|
390
|
+
# @param samples [Array<Float>] Sampled iterations per second.
|
331
391
|
# @param cycles [Integer] Number of Cycles.
|
332
|
-
|
333
|
-
|
392
|
+
# @return [Report::Entry] Entry with data.
|
393
|
+
def create_report(label, measured_us, iter, samples, cycles)
|
394
|
+
@full_report.add_entry label, measured_us, iter, samples, cycles
|
334
395
|
end
|
335
396
|
end
|
336
397
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Benchmark
|
2
|
+
module IPS
|
3
|
+
class NoopSuite
|
4
|
+
def start_warming
|
5
|
+
end
|
6
|
+
|
7
|
+
def start_running
|
8
|
+
end
|
9
|
+
|
10
|
+
def footer
|
11
|
+
end
|
12
|
+
|
13
|
+
def warming(a, b)
|
14
|
+
end
|
15
|
+
|
16
|
+
def warmup_stats(a, b)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_report(a, b)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :running, :warming
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/benchmark/ips/report.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Benchmark
|
4
4
|
module IPS
|
5
5
|
|
6
|
-
# Report contains
|
6
|
+
# Report contains benchmarking entries.
|
7
7
|
# Perform operations like add new entry, run comparison between entries.
|
8
8
|
class Report
|
9
9
|
|
@@ -13,15 +13,13 @@ module Benchmark
|
|
13
13
|
# @param [#to_s] label Label of entry.
|
14
14
|
# @param [Integer] us Measured time in microsecond.
|
15
15
|
# @param [Integer] iters Iterations.
|
16
|
-
# @param [
|
17
|
-
# @param [Float] ips_sd Standard deviation of iterations per second.
|
16
|
+
# @param [Object] stats Statistics.
|
18
17
|
# @param [Integer] cycles Number of Cycles.
|
19
|
-
def initialize(label, us, iters,
|
18
|
+
def initialize(label, us, iters, stats, cycles)
|
20
19
|
@label = label
|
21
20
|
@microseconds = us
|
22
21
|
@iterations = iters
|
23
|
-
@
|
24
|
-
@ips_sd = ips_sd
|
22
|
+
@stats = stats
|
25
23
|
@measurement_cycle = cycles
|
26
24
|
@show_total_time = false
|
27
25
|
end
|
@@ -38,13 +36,25 @@ module Benchmark
|
|
38
36
|
# @return [Integer] number of iterations.
|
39
37
|
attr_reader :iterations
|
40
38
|
|
41
|
-
#
|
39
|
+
# Statistical summary of samples.
|
40
|
+
# @return [Object] statisical summary.
|
41
|
+
attr_reader :stats
|
42
|
+
|
43
|
+
# LEGACY: Iterations per second.
|
42
44
|
# @return [Float] number of iterations per second.
|
43
|
-
|
45
|
+
def ips
|
46
|
+
@stats.central_tendency
|
47
|
+
end
|
44
48
|
|
45
|
-
# Standard deviation of iteration per second.
|
49
|
+
# LEGACY: Standard deviation of iteration per second.
|
46
50
|
# @return [Float] standard deviation of iteration per second.
|
47
|
-
|
51
|
+
def ips_sd
|
52
|
+
@stats.error
|
53
|
+
end
|
54
|
+
|
55
|
+
def samples
|
56
|
+
@stats.samples
|
57
|
+
end
|
48
58
|
|
49
59
|
# Number of Cycles.
|
50
60
|
# @return [Integer] number of cycles.
|
@@ -65,8 +75,8 @@ module Benchmark
|
|
65
75
|
|
66
76
|
# Return entry's standard deviation of iteration per second in percentage.
|
67
77
|
# @return [Float] +@ips_sd+ in percentage.
|
68
|
-
def
|
69
|
-
|
78
|
+
def error_percentage
|
79
|
+
@stats.error_percentage
|
70
80
|
end
|
71
81
|
|
72
82
|
alias_method :runtime, :seconds
|
@@ -78,7 +88,7 @@ module Benchmark
|
|
78
88
|
def body
|
79
89
|
case Benchmark::IPS.options[:format]
|
80
90
|
when :human
|
81
|
-
left = "%s (±%4.1f%%) i/s" % [Helpers.scale(
|
91
|
+
left = "%s (±%4.1f%%) i/s" % [Helpers.scale(@stats.central_tendency), @stats.error_percentage]
|
82
92
|
iters = Helpers.scale(@iterations)
|
83
93
|
|
84
94
|
if @show_total_time
|
@@ -87,7 +97,7 @@ module Benchmark
|
|
87
97
|
left.ljust(20) + (" - %s" % iters)
|
88
98
|
end
|
89
99
|
else
|
90
|
-
left = "%10.1f (±%.1f%%) i/s" % [
|
100
|
+
left = "%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, @stats.error_percentage]
|
91
101
|
|
92
102
|
if @show_total_time
|
93
103
|
left.ljust(20) + (" - %10d in %10.6fs" % [@iterations, runtime])
|
@@ -117,8 +127,8 @@ module Benchmark
|
|
117
127
|
|
118
128
|
# class Report
|
119
129
|
|
120
|
-
# Entry to represent each
|
121
|
-
# @return [Array<Entry>] Entries in Report.
|
130
|
+
# Entry to represent each benchmarked code in Report.
|
131
|
+
# @return [Array<Report::Entry>] Entries in Report.
|
122
132
|
attr_reader :entries
|
123
133
|
|
124
134
|
# Instantiate the Report.
|
@@ -131,13 +141,14 @@ module Benchmark
|
|
131
141
|
# @param label [String] Entry label.
|
132
142
|
# @param microseconds [Integer] Measured time in microsecond.
|
133
143
|
# @param iters [Integer] Iterations.
|
134
|
-
# @param
|
135
|
-
# @param ips_sd [Float] Standard deviation of iterations per second.
|
144
|
+
# @param stats [Object] Statistical results.
|
136
145
|
# @param measurement_cycle [Integer] Number of cycles.
|
137
|
-
# @return [Entry] Last added entry.
|
138
|
-
def add_entry label, microseconds, iters,
|
139
|
-
|
140
|
-
@entries.
|
146
|
+
# @return [Report::Entry] Last added entry.
|
147
|
+
def add_entry label, microseconds, iters, stats, measurement_cycle
|
148
|
+
entry = Entry.new(label, microseconds, iters, stats, measurement_cycle)
|
149
|
+
@entries.delete_if { |e| e.label == label }
|
150
|
+
@entries << entry
|
151
|
+
entry
|
141
152
|
end
|
142
153
|
|
143
154
|
# Entries data in array for generate json.
|
@@ -145,28 +156,40 @@ module Benchmark
|
|
145
156
|
# name: Entry#label
|
146
157
|
# ips: Entry#ips
|
147
158
|
# stddev: Entry#ips_sd
|
148
|
-
#
|
159
|
+
# microseconds: Entry#microseconds
|
160
|
+
# iterations: Entry#iterations
|
161
|
+
# cycles: Entry#measurement_cycles
|
162
|
+
# @return [Array<Hash<Symbol,String|Float|Integer>] Array of hashes
|
149
163
|
def data
|
150
164
|
@data ||= @entries.collect do |entry|
|
151
165
|
{
|
152
166
|
:name => entry.label,
|
153
|
-
:
|
154
|
-
:
|
167
|
+
:central_tendency => entry.stats.central_tendency,
|
168
|
+
:ips => entry.stats.central_tendency, # for backwards compatibility
|
169
|
+
:error => entry.stats.error,
|
170
|
+
:stddev => entry.stats.error, # for backwards compatibility
|
171
|
+
:microseconds => entry.microseconds,
|
172
|
+
:iterations => entry.iterations,
|
173
|
+
:cycles => entry.measurement_cycle,
|
155
174
|
}
|
156
175
|
end
|
157
176
|
end
|
158
177
|
|
159
178
|
# Run comparison of entries.
|
160
|
-
def run_comparison
|
161
|
-
Benchmark.compare(*@entries)
|
179
|
+
def run_comparison(order)
|
180
|
+
Benchmark.compare(*@entries, order: order)
|
162
181
|
end
|
163
182
|
|
164
183
|
# Generate json from Report#data to given path.
|
165
184
|
# @param path [String] path to generate json.
|
166
185
|
def generate_json(path)
|
167
|
-
|
168
|
-
|
169
|
-
|
186
|
+
require "json"
|
187
|
+
if path.respond_to?(:write) # STDOUT
|
188
|
+
path.write JSON.pretty_generate(data)
|
189
|
+
else
|
190
|
+
File.open path, "w" do |f|
|
191
|
+
f.write JSON.pretty_generate(data)
|
192
|
+
end
|
170
193
|
end
|
171
194
|
end
|
172
195
|
end
|