benchmark-ips 2.7.2 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/History.txt +35 -0
- data/Manifest.txt +1 -1
- data/README.md +18 -8
- data/lib/benchmark/compare.rb +5 -9
- data/lib/benchmark/ips.rb +79 -14
- data/lib/benchmark/ips/job.rb +131 -85
- data/lib/benchmark/ips/job/entry.rb +33 -15
- data/lib/benchmark/ips/job/stdout_report.rb +6 -1
- data/lib/benchmark/ips/report.rb +7 -3
- data/lib/benchmark/ips/share.rb +3 -1
- data/lib/benchmark/ips/stats/bootstrap.rb +9 -6
- data/lib/benchmark/ips/stats/sd.rb +19 -11
- data/lib/benchmark/ips/stats/stats_metric.rb +21 -0
- data/test/test_benchmark_ips.rb +60 -0
- metadata +27 -21
- data/Gemfile.lock +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5bec7b23527f8a5f331788f9de05073eede35e26bbc5a6b81c05a609d964c0ff
|
4
|
+
data.tar.gz: aa8ba81457df018d94262d0eb722870d34406204d02a61b0ad1aa398fa2903ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b8c313bd722fd52229c280d4d566fb9e246b8ae4253a0f508a5b9ca7fffd35cd780cc6c9aa96894733ee87d0df844feae530c10dbf38989cb07367094ce19f3
|
7
|
+
data.tar.gz: 037a07ffd3f129a06a68b5c53617379a9c7e3397fc9b02a961c7e7bb9b566f3ad0b423a43b3b5ed3e40f9edfac4f6eed810bc231c8e6d1398afaa65730d62946
|
data/History.txt
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
=== 2.9.0 / 2021-05-21
|
2
|
+
|
3
|
+
* Features
|
4
|
+
* Suite can now be set via an accessor
|
5
|
+
* Default SHARE_URL is now `ips.fastruby.io`, operated by Ombu Labs.
|
6
|
+
|
7
|
+
=== 2.8.4 / 2020-12-03
|
8
|
+
|
9
|
+
* Bug fix
|
10
|
+
* Fixed hold! when results file does not exist.
|
11
|
+
|
12
|
+
=== 2.8.3 / 2020-08-28
|
13
|
+
|
14
|
+
* Bug fix
|
15
|
+
* Fixed inaccuracy caused by integer overflows.
|
16
|
+
|
17
|
+
=== 2.8.2 / 2020-05-04
|
18
|
+
|
19
|
+
* Bug fix
|
20
|
+
* Fixed problems with Manifest.txt.
|
21
|
+
* Empty interim results files are ignored.
|
22
|
+
|
23
|
+
=== 2.8.0 / 2020-05-01
|
24
|
+
|
25
|
+
* Feature
|
26
|
+
* Allow running with empty ips block.
|
27
|
+
* Added save! method for saving interim results.
|
28
|
+
* Run more than just 1 cycle during warmup to reduce overhead.
|
29
|
+
* Optimized Job::Entry hot-path for fairer results on JRuby/TruffleRuby.
|
30
|
+
|
31
|
+
* Bug fix
|
32
|
+
* Removed the warmup section if set to 0.
|
33
|
+
* Added some RDoc docs.
|
34
|
+
* Added some examples in examples/
|
35
|
+
|
1
36
|
=== 2.7.2 / 2016-08-18
|
2
37
|
|
3
38
|
* 1 bug fix:
|
data/Manifest.txt
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
.autotest
|
2
|
-
Gemfile.lock
|
3
2
|
History.txt
|
4
3
|
Manifest.txt
|
5
4
|
README.md
|
@@ -13,5 +12,6 @@ lib/benchmark/ips/report.rb
|
|
13
12
|
lib/benchmark/ips/share.rb
|
14
13
|
lib/benchmark/ips/stats/bootstrap.rb
|
15
14
|
lib/benchmark/ips/stats/sd.rb
|
15
|
+
lib/benchmark/ips/stats/stats_metric.rb
|
16
16
|
lib/benchmark/timing.rb
|
17
17
|
test/test_benchmark_ips.rb
|
data/README.md
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
+
# benchmark-ips
|
2
|
+
|
3
|
+
* rdoc :: http://rubydoc.info/gems/benchmark-ips
|
4
|
+
* home :: https://github.com/evanphx/benchmark-ips
|
5
|
+
|
1
6
|
[![Gem Version](https://badge.fury.io/rb/benchmark-ips.svg)](http://badge.fury.io/rb/benchmark-ips)
|
2
7
|
[![Build Status](https://secure.travis-ci.org/evanphx/benchmark-ips.svg)](http://travis-ci.org/evanphx/benchmark-ips)
|
3
8
|
[![Inline docs](http://inch-ci.org/github/evanphx/benchmark-ips.svg)](http://inch-ci.org/github/evanphx/benchmark-ips)
|
4
9
|
|
5
|
-
# benchmark-ips
|
6
|
-
|
7
10
|
* https://github.com/evanphx/benchmark-ips
|
8
11
|
|
9
|
-
* [documentation](http://rubydoc.info/gems/benchmark-ips)
|
10
|
-
|
11
12
|
## DESCRIPTION:
|
12
13
|
|
13
14
|
An iterations per second enhancement to Benchmark.
|
@@ -155,6 +156,11 @@ This will run only one benchmarks each time you run the command, storing
|
|
155
156
|
results in the specified file. The file is deleted when all results have been
|
156
157
|
gathered and the report is shown.
|
157
158
|
|
159
|
+
Alternatively, if you prefer a different approach, the `save!` command is
|
160
|
+
available. Examples for [hold!](examples/hold.rb) and [save!](examples/save.rb) are available in
|
161
|
+
the `examples/` directory.
|
162
|
+
|
163
|
+
|
158
164
|
### Multiple iterations
|
159
165
|
|
160
166
|
In some cases you may want to run multiple iterations of the warmup and
|
@@ -180,11 +186,15 @@ end
|
|
180
186
|
|
181
187
|
### Online sharing
|
182
188
|
|
183
|
-
If you want to share
|
184
|
-
with `SHARE=1` argument.
|
185
|
-
|
189
|
+
If you want to quickly share your benchmark result with others, run you benchmark
|
190
|
+
with `SHARE=1` argument. For example: `SHARE=1 ruby my_benchmark.rb`.
|
191
|
+
|
192
|
+
Result will be sent to [benchmark.fyi](https://ips.fastruby.io/) and benchmark-ips
|
186
193
|
will display the link to share the benchmark's result.
|
187
194
|
|
195
|
+
If you want to run your own instance of [benchmark.fyi](https://github.com/evanphx/benchmark.fyi)
|
196
|
+
and share it to that instance, you can do this: `SHARE_URL=https://ips.example.com ruby my_benchmark.rb`
|
197
|
+
|
188
198
|
### Advanced Statistics
|
189
199
|
|
190
200
|
By default, the margin of error shown is plus-minus one standard deviation. If
|
@@ -221,7 +231,7 @@ Benchmark.ips do |x|
|
|
221
231
|
|
222
232
|
x.stats = :bootstrap
|
223
233
|
x.confidence = 95
|
224
|
-
|
234
|
+
|
225
235
|
# confidence is 95% by default, so it can be omitted
|
226
236
|
|
227
237
|
end
|
data/lib/benchmark/compare.rb
CHANGED
@@ -40,18 +40,14 @@ module Benchmark
|
|
40
40
|
|
41
41
|
$stdout.puts "\nComparison:"
|
42
42
|
|
43
|
-
$stdout.printf "%20s: %10.1f i/s\n", best.label, best.stats.central_tendency
|
43
|
+
$stdout.printf "%20s: %10.1f i/s\n", best.label.to_s, best.stats.central_tendency
|
44
44
|
|
45
45
|
sorted.each do |report|
|
46
46
|
name = report.label.to_s
|
47
|
-
|
47
|
+
|
48
48
|
$stdout.printf "%20s: %10.1f i/s - ", name, report.stats.central_tendency
|
49
|
-
|
50
|
-
|
51
|
-
report_high = report.stats.central_tendency + report.stats.error
|
52
|
-
overlaps = report_high > best_low
|
53
|
-
|
54
|
-
if overlaps
|
49
|
+
|
50
|
+
if report.stats.overlaps?(best.stats)
|
55
51
|
$stdout.print "same-ish: difference falls within error"
|
56
52
|
else
|
57
53
|
slowdown, error = report.stats.slowdown(best.stats)
|
@@ -61,7 +57,7 @@ module Benchmark
|
|
61
57
|
end
|
62
58
|
$stdout.print " slower"
|
63
59
|
end
|
64
|
-
|
60
|
+
|
65
61
|
$stdout.puts
|
66
62
|
end
|
67
63
|
|
data/lib/benchmark/ips.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'benchmark/timing'
|
3
3
|
require 'benchmark/compare'
|
4
|
+
require 'benchmark/ips/stats/stats_metric'
|
4
5
|
require 'benchmark/ips/stats/sd'
|
5
6
|
require 'benchmark/ips/stats/bootstrap'
|
6
7
|
require 'benchmark/ips/report'
|
8
|
+
require 'benchmark/ips/noop_suite'
|
7
9
|
require 'benchmark/ips/job/entry'
|
8
10
|
require 'benchmark/ips/job/stdout_report'
|
11
|
+
require 'benchmark/ips/job/noop_report'
|
9
12
|
require 'benchmark/ips/job'
|
10
13
|
|
11
14
|
# Performance benchmarking library
|
@@ -15,10 +18,10 @@ module Benchmark
|
|
15
18
|
module IPS
|
16
19
|
|
17
20
|
# Benchmark-ips Gem version.
|
18
|
-
VERSION = "2.
|
21
|
+
VERSION = "2.9.0"
|
19
22
|
|
20
23
|
# CODENAME of current version.
|
21
|
-
CODENAME = "
|
24
|
+
CODENAME = "Sleepy Sasquatch"
|
22
25
|
|
23
26
|
# Measure code in block, each code's benchmarked result will display in
|
24
27
|
# iteration per second with standard deviation in given time.
|
@@ -32,32 +35,30 @@ module Benchmark
|
|
32
35
|
time, warmup, quiet = args
|
33
36
|
end
|
34
37
|
|
35
|
-
suite = nil
|
36
|
-
|
37
38
|
sync, $stdout.sync = $stdout.sync, true
|
38
39
|
|
39
|
-
|
40
|
-
suite = Benchmark::Suite.current
|
41
|
-
end
|
42
|
-
|
43
|
-
quiet ||= (suite && suite.quiet?)
|
44
|
-
|
45
|
-
job = Job.new({:suite => suite,
|
46
|
-
:quiet => quiet
|
47
|
-
})
|
40
|
+
job = Job.new
|
48
41
|
|
49
42
|
job_opts = {}
|
50
43
|
job_opts[:time] = time unless time.nil?
|
51
44
|
job_opts[:warmup] = warmup unless warmup.nil?
|
45
|
+
job_opts[:quiet] = quiet unless quiet.nil?
|
52
46
|
|
53
47
|
job.config job_opts
|
54
48
|
|
55
49
|
yield job
|
56
50
|
|
57
|
-
job.load_held_results
|
51
|
+
job.load_held_results
|
58
52
|
|
59
53
|
job.run
|
60
54
|
|
55
|
+
if job.run_single? && job.all_results_have_been_run?
|
56
|
+
job.clear_held_results
|
57
|
+
else
|
58
|
+
job.save_held_results
|
59
|
+
puts '', 'Pausing here -- run Ruby again to measure the next benchmark...' if job.run_single?
|
60
|
+
end
|
61
|
+
|
61
62
|
$stdout.sync = sync
|
62
63
|
job.run_comparison
|
63
64
|
job.generate_json
|
@@ -102,4 +103,68 @@ module Benchmark
|
|
102
103
|
end
|
103
104
|
|
104
105
|
extend Benchmark::IPS # make ips available as module-level method
|
106
|
+
|
107
|
+
##
|
108
|
+
# :singleton-method: ips
|
109
|
+
#
|
110
|
+
# require 'benchmark/ips'
|
111
|
+
#
|
112
|
+
# Benchmark.ips do |x|
|
113
|
+
# # Configure the number of seconds used during
|
114
|
+
# # the warmup phase (default 2) and calculation phase (default 5)
|
115
|
+
# x.config(:time => 5, :warmup => 2)
|
116
|
+
#
|
117
|
+
# # These parameters can also be configured this way
|
118
|
+
# x.time = 5
|
119
|
+
# x.warmup = 2
|
120
|
+
#
|
121
|
+
# # Typical mode, runs the block as many times as it can
|
122
|
+
# x.report("addition") { 1 + 2 }
|
123
|
+
#
|
124
|
+
# # To reduce overhead, the number of iterations is passed in
|
125
|
+
# # and the block must run the code the specific number of times.
|
126
|
+
# # Used for when the workload is very small and any overhead
|
127
|
+
# # introduces incorrectable errors.
|
128
|
+
# x.report("addition2") do |times|
|
129
|
+
# i = 0
|
130
|
+
# while i < times
|
131
|
+
# 1 + 2
|
132
|
+
# i += 1
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# # To reduce overhead even more, grafts the code given into
|
137
|
+
# # the loop that performs the iterations internally to reduce
|
138
|
+
# # overhead. Typically not needed, use the |times| form instead.
|
139
|
+
# x.report("addition3", "1 + 2")
|
140
|
+
#
|
141
|
+
# # Really long labels should be formatted correctly
|
142
|
+
# x.report("addition-test-long-label") { 1 + 2 }
|
143
|
+
#
|
144
|
+
# # Compare the iterations per second of the various reports!
|
145
|
+
# x.compare!
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# This will generate the following report:
|
149
|
+
#
|
150
|
+
# Calculating -------------------------------------
|
151
|
+
# addition 71.254k i/100ms
|
152
|
+
# addition2 68.658k i/100ms
|
153
|
+
# addition3 83.079k i/100ms
|
154
|
+
# addition-test-long-label
|
155
|
+
# 70.129k i/100ms
|
156
|
+
# -------------------------------------------------
|
157
|
+
# addition 4.955M (± 8.7%) i/s - 24.155M
|
158
|
+
# addition2 24.011M (± 9.5%) i/s - 114.246M
|
159
|
+
# addition3 23.958M (±10.1%) i/s - 115.064M
|
160
|
+
# addition-test-long-label
|
161
|
+
# 5.014M (± 9.1%) i/s - 24.545M
|
162
|
+
#
|
163
|
+
# Comparison:
|
164
|
+
# addition2: 24011974.8 i/s
|
165
|
+
# addition3: 23958619.8 i/s - 1.00x slower
|
166
|
+
# addition-test-long-label: 5014756.0 i/s - 4.79x slower
|
167
|
+
# addition: 4955278.9 i/s - 4.85x slower
|
168
|
+
#
|
169
|
+
# See also Benchmark::IPS
|
105
170
|
end
|
data/lib/benchmark/ips/job.rb
CHANGED
@@ -9,6 +9,7 @@ module Benchmark
|
|
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
|
+
POW_2_30 = 1 << 30
|
12
13
|
|
13
14
|
# Two-element arrays, consisting of label and block pairs.
|
14
15
|
# @return [Array<Entry>] list of entries
|
@@ -50,19 +51,24 @@ module Benchmark
|
|
50
51
|
# @return [Integer]
|
51
52
|
attr_accessor :confidence
|
52
53
|
|
54
|
+
# Silence output
|
55
|
+
# @return [Boolean]
|
56
|
+
attr_reader :quiet
|
57
|
+
|
58
|
+
# Suite
|
59
|
+
# @return [Benchmark::IPS::NoopSuite]
|
60
|
+
attr_reader :suite
|
61
|
+
|
53
62
|
# Instantiate the Benchmark::IPS::Job.
|
54
|
-
# @option opts [Benchmark::Suite] (nil) :suite Specify Benchmark::Suite.
|
55
|
-
# @option opts [Boolean] (false) :quiet Suppress the printing of information.
|
56
63
|
def initialize opts={}
|
57
|
-
@suite = opts[:suite] || nil
|
58
|
-
@stdout = opts[:quiet] ? nil : StdoutReport.new
|
59
64
|
@list = []
|
60
|
-
@
|
65
|
+
@run_single = false
|
61
66
|
@json_path = false
|
67
|
+
@compare = false
|
62
68
|
@held_path = nil
|
63
69
|
@held_results = nil
|
64
70
|
|
65
|
-
@timing =
|
71
|
+
@timing = Hash.new 1 # default to 1 in case warmup isn't run
|
66
72
|
@full_report = Report.new
|
67
73
|
|
68
74
|
# Default warmup and calculation time in seconds.
|
@@ -86,6 +92,20 @@ module Benchmark
|
|
86
92
|
@iterations = opts[:iterations] if opts[:iterations]
|
87
93
|
@stats = opts[:stats] if opts[:stats]
|
88
94
|
@confidence = opts[:confidence] if opts[:confidence]
|
95
|
+
self.quiet = opts[:quiet]
|
96
|
+
self.suite = opts[:suite]
|
97
|
+
end
|
98
|
+
|
99
|
+
def quiet=(val)
|
100
|
+
@stdout = reporter(quiet: val)
|
101
|
+
end
|
102
|
+
|
103
|
+
def suite=(suite)
|
104
|
+
@suite = suite || Benchmark::IPS::NoopSuite.new
|
105
|
+
end
|
106
|
+
|
107
|
+
def reporter(quiet:)
|
108
|
+
quiet ? NoopReport.new : StdoutReport.new
|
89
109
|
end
|
90
110
|
|
91
111
|
# Return true if job needs to be compared.
|
@@ -94,7 +114,7 @@ module Benchmark
|
|
94
114
|
@compare
|
95
115
|
end
|
96
116
|
|
97
|
-
#
|
117
|
+
# Run comparison utility.
|
98
118
|
def compare!
|
99
119
|
@compare = true
|
100
120
|
end
|
@@ -105,9 +125,27 @@ module Benchmark
|
|
105
125
|
!!@held_path
|
106
126
|
end
|
107
127
|
|
108
|
-
#
|
128
|
+
# Hold after each iteration.
|
129
|
+
# @param held_path [String] File name to store hold file.
|
109
130
|
def hold!(held_path)
|
110
131
|
@held_path = held_path
|
132
|
+
@run_single = true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Save interim results. Similar to hold, but all reports are run
|
136
|
+
# The report label must change for each invocation.
|
137
|
+
# One way to achieve this is to include the version in the label.
|
138
|
+
# @param held_path [String] File name to store hold file.
|
139
|
+
def save!(held_path)
|
140
|
+
@held_path = held_path
|
141
|
+
@run_single = false
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return true if items are to be run one at a time.
|
145
|
+
# For the traditional hold, this is true
|
146
|
+
# @return [Boolean] Run just a single item?
|
147
|
+
def run_single?
|
148
|
+
@run_single
|
111
149
|
end
|
112
150
|
|
113
151
|
# Return true if job needs to generate json.
|
@@ -116,7 +154,7 @@ module Benchmark
|
|
116
154
|
!!@json_path
|
117
155
|
end
|
118
156
|
|
119
|
-
#
|
157
|
+
# Generate json to given path, defaults to "data.json".
|
120
158
|
def json!(path="data.json")
|
121
159
|
@json_path = path
|
122
160
|
end
|
@@ -166,86 +204,114 @@ module Benchmark
|
|
166
204
|
def iterations_per_sec cycles, time_us
|
167
205
|
MICROSECONDS_PER_SECOND * (cycles.to_f / time_us.to_f)
|
168
206
|
end
|
169
|
-
|
170
|
-
def held_results?
|
171
|
-
File.exist?(@held_path)
|
172
|
-
end
|
173
|
-
|
207
|
+
|
174
208
|
def load_held_results
|
209
|
+
return unless @held_path && File.exist?(@held_path) && !File.zero?(@held_path)
|
175
210
|
require "json"
|
176
|
-
@held_results =
|
177
|
-
|
178
|
-
[result['item']
|
179
|
-
|
211
|
+
@held_results = {}
|
212
|
+
JSON.load(IO.read(@held_path)).each do |result|
|
213
|
+
@held_results[result['item']] = result
|
214
|
+
create_report(result['item'], result['measured_us'], result['iter'],
|
215
|
+
create_stats(result['samples']), result['cycles'])
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def save_held_results
|
220
|
+
return unless @held_path
|
221
|
+
require "json"
|
222
|
+
data = full_report.entries.map { |e|
|
223
|
+
{
|
224
|
+
'item' => e.label,
|
225
|
+
'measured_us' => e.microseconds,
|
226
|
+
'iter' => e.iterations,
|
227
|
+
'samples' => e.samples,
|
228
|
+
'cycles' => e.measurement_cycle
|
229
|
+
}
|
230
|
+
}
|
231
|
+
IO.write(@held_path, JSON.generate(data) << "\n")
|
180
232
|
end
|
181
|
-
|
233
|
+
|
234
|
+
def all_results_have_been_run?
|
235
|
+
@full_report.entries.size == @list.size
|
236
|
+
end
|
237
|
+
|
238
|
+
def clear_held_results
|
239
|
+
File.delete @held_path if File.exist?(@held_path)
|
240
|
+
end
|
241
|
+
|
182
242
|
def run
|
183
|
-
@
|
184
|
-
|
185
|
-
|
243
|
+
if @warmup && @warmup != 0 then
|
244
|
+
@stdout.start_warming
|
245
|
+
@iterations.times do
|
246
|
+
run_warmup
|
247
|
+
end
|
186
248
|
end
|
187
|
-
|
188
|
-
@stdout.start_running
|
189
|
-
|
190
|
-
held = nil
|
191
|
-
|
249
|
+
|
250
|
+
@stdout.start_running
|
251
|
+
|
192
252
|
@iterations.times do |n|
|
193
|
-
|
253
|
+
run_benchmark
|
194
254
|
end
|
195
255
|
|
196
|
-
@stdout.footer
|
197
|
-
|
198
|
-
if held
|
199
|
-
puts
|
200
|
-
puts 'Pausing here -- run Ruby again to measure the next benchmark...'
|
201
|
-
end
|
256
|
+
@stdout.footer
|
202
257
|
end
|
203
258
|
|
204
259
|
# Run warmup.
|
205
260
|
def run_warmup
|
206
261
|
@list.each do |item|
|
207
|
-
next if
|
208
|
-
|
209
|
-
@suite.warming item.label, @warmup
|
210
|
-
@stdout.warming item.label, @warmup
|
262
|
+
next if run_single? && @held_results && @held_results.key?(item.label)
|
263
|
+
|
264
|
+
@suite.warming item.label, @warmup
|
265
|
+
@stdout.warming item.label, @warmup
|
211
266
|
|
212
267
|
Timing.clean_env
|
213
268
|
|
269
|
+
# Run for up to half of the configured warmup time with an increasing
|
270
|
+
# number of cycles to reduce overhead and improve accuracy.
|
271
|
+
# This also avoids running with a constant number of cycles, which a
|
272
|
+
# JIT might speculate on and then have to recompile in #run_benchmark.
|
214
273
|
before = Timing.now
|
215
|
-
target = Timing.add_second before, @warmup
|
274
|
+
target = Timing.add_second before, @warmup / 2.0
|
216
275
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
276
|
+
cycles = 1
|
277
|
+
warmup_iter = 1
|
278
|
+
warmup_time_us = 0.0
|
279
|
+
while Timing.now + warmup_time_us * 2 < target
|
280
|
+
t0 = Timing.now
|
281
|
+
item.call_times cycles
|
282
|
+
t1 = Timing.now
|
283
|
+
warmup_iter = cycles
|
284
|
+
warmup_time_us = Timing.time_us(t0, t1)
|
285
|
+
|
286
|
+
# If the number of cycles would go outside the 32-bit signed integers range
|
287
|
+
# then exit the loop to avoid overflows and start the 100ms warmup runs
|
288
|
+
break if cycles >= POW_2_30
|
289
|
+
cycles *= 2
|
222
290
|
end
|
223
291
|
|
224
|
-
|
292
|
+
cycles = cycles_per_100ms warmup_time_us, warmup_iter
|
293
|
+
@timing[item] = cycles
|
225
294
|
|
226
|
-
|
295
|
+
# Run for the remaining of warmup in a similar way as #run_benchmark.
|
296
|
+
target = Timing.add_second before, @warmup
|
297
|
+
while Timing.now + MICROSECONDS_PER_100MS < target
|
298
|
+
item.call_times cycles
|
299
|
+
end
|
227
300
|
|
228
|
-
@timing[item]
|
301
|
+
@stdout.warmup_stats warmup_time_us, @timing[item]
|
302
|
+
@suite.warmup_stats warmup_time_us, @timing[item]
|
229
303
|
|
230
|
-
|
231
|
-
@suite.warmup_stats warmup_time_us, @timing[item] if @suite
|
232
|
-
|
233
|
-
break if hold?
|
304
|
+
break if run_single?
|
234
305
|
end
|
235
306
|
end
|
236
307
|
|
237
308
|
# Run calculation.
|
238
309
|
def run_benchmark
|
239
310
|
@list.each do |item|
|
240
|
-
if
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
next
|
245
|
-
end
|
246
|
-
|
247
|
-
@suite.running item.label, @time if @suite
|
248
|
-
@stdout.running item.label, @time if @stdout
|
311
|
+
next if run_single? && @held_results && @held_results.key?(item.label)
|
312
|
+
|
313
|
+
@suite.running item.label, @time
|
314
|
+
@stdout.running item.label, @time
|
249
315
|
|
250
316
|
Timing.clean_env
|
251
317
|
|
@@ -257,7 +323,7 @@ module Benchmark
|
|
257
323
|
cycles = @timing[item]
|
258
324
|
|
259
325
|
target = Timing.add_second Timing.now, @time
|
260
|
-
|
326
|
+
|
261
327
|
while (before = Timing.now) < target
|
262
328
|
item.call_times cycles
|
263
329
|
after = Timing.now
|
@@ -286,31 +352,11 @@ module Benchmark
|
|
286
352
|
rep.show_total_time!
|
287
353
|
end
|
288
354
|
|
289
|
-
@stdout.add_report rep, caller(1).first
|
290
|
-
@suite.add_report rep, caller(1).first
|
291
|
-
|
292
|
-
if
|
293
|
-
File.open @held_path, "a" do |f|
|
294
|
-
require "json"
|
295
|
-
f.write JSON.generate({
|
296
|
-
:item => item.label,
|
297
|
-
:measured_us => measured_us,
|
298
|
-
:iter => iter,
|
299
|
-
:samples => samples,
|
300
|
-
:cycles => cycles
|
301
|
-
})
|
302
|
-
f.write "\n"
|
303
|
-
end
|
304
|
-
|
305
|
-
return true
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
if hold? && @full_report.entries.size == @list.size
|
310
|
-
File.delete @held_path if File.exist?(@held_path)
|
355
|
+
@stdout.add_report rep, caller(1).first
|
356
|
+
@suite.add_report rep, caller(1).first
|
357
|
+
|
358
|
+
break if run_single?
|
311
359
|
end
|
312
|
-
|
313
|
-
false
|
314
360
|
end
|
315
361
|
|
316
362
|
def create_stats(samples)
|
@@ -11,10 +11,12 @@ module Benchmark
|
|
11
11
|
def initialize(label, action)
|
12
12
|
@label = label
|
13
13
|
|
14
|
+
# We define #call_times on the singleton class of each Entry instance.
|
15
|
+
# That way, there is no polymorphism for `@action.call` inside #call_times.
|
16
|
+
|
14
17
|
if action.kind_of? String
|
15
|
-
|
18
|
+
compile_string action
|
16
19
|
@action = self
|
17
|
-
@as_action = true
|
18
20
|
else
|
19
21
|
unless action.respond_to? :call
|
20
22
|
raise ArgumentError, "invalid action, must respond to #call"
|
@@ -23,12 +25,10 @@ module Benchmark
|
|
23
25
|
@action = action
|
24
26
|
|
25
27
|
if action.respond_to? :arity and action.arity > 0
|
26
|
-
|
28
|
+
compile_block_with_manual_loop
|
27
29
|
else
|
28
|
-
|
30
|
+
compile_block
|
29
31
|
end
|
30
|
-
|
31
|
-
@as_action = false
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -40,25 +40,43 @@ module Benchmark
|
|
40
40
|
# @return [String, Proc] Code to be called, could be String / Proc.
|
41
41
|
attr_reader :action
|
42
42
|
|
43
|
-
# Call action by given times
|
43
|
+
# Call action by given times.
|
44
44
|
# @param times [Integer] Times to call +@action+.
|
45
45
|
# @return [Integer] Number of times the +@action+ has been called.
|
46
46
|
def call_times(times)
|
47
|
-
|
47
|
+
raise '#call_times should be redefined per Benchmark::IPS::Job::Entry instance'
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
+
def compile_block
|
51
|
+
m = (class << self; self; end)
|
52
|
+
code = <<-CODE
|
53
|
+
def call_times(times)
|
54
|
+
act = @action
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
i = 0
|
57
|
+
while i < times
|
58
|
+
act.call
|
59
|
+
i += 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
CODE
|
63
|
+
m.class_eval code
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile_block_with_manual_loop
|
67
|
+
m = (class << self; self; end)
|
68
|
+
code = <<-CODE
|
69
|
+
def call_times(times)
|
70
|
+
@action.call(times)
|
71
|
+
end
|
72
|
+
CODE
|
73
|
+
m.class_eval code
|
56
74
|
end
|
57
75
|
|
58
76
|
# Compile code into +call_times+ method.
|
59
77
|
# @param str [String] Code to be compiled.
|
60
78
|
# @return [Symbol] :call_times.
|
61
|
-
def
|
79
|
+
def compile_string(str)
|
62
80
|
m = (class << self; self; end)
|
63
81
|
code = <<-CODE
|
64
82
|
def call_times(__total);
|
@@ -2,10 +2,14 @@ module Benchmark
|
|
2
2
|
module IPS
|
3
3
|
class Job
|
4
4
|
class StdoutReport
|
5
|
+
def initialize
|
6
|
+
@last_item = nil
|
7
|
+
end
|
8
|
+
|
5
9
|
def start_warming
|
6
10
|
$stdout.puts "Warming up --------------------------------------"
|
7
11
|
end
|
8
|
-
|
12
|
+
|
9
13
|
def start_running
|
10
14
|
$stdout.puts "Calculating -------------------------------------"
|
11
15
|
end
|
@@ -31,6 +35,7 @@ module Benchmark
|
|
31
35
|
end
|
32
36
|
|
33
37
|
def footer
|
38
|
+
return unless @last_item
|
34
39
|
footer = @last_item.stats.footer
|
35
40
|
$stdout.puts footer.rjust(40) if footer
|
36
41
|
end
|
data/lib/benchmark/ips/report.rb
CHANGED
@@ -52,6 +52,10 @@ module Benchmark
|
|
52
52
|
@stats.error
|
53
53
|
end
|
54
54
|
|
55
|
+
def samples
|
56
|
+
@stats.samples
|
57
|
+
end
|
58
|
+
|
55
59
|
# Number of Cycles.
|
56
60
|
# @return [Integer] number of cycles.
|
57
61
|
attr_reader :measurement_cycle
|
@@ -72,7 +76,7 @@ module Benchmark
|
|
72
76
|
# Return entry's standard deviation of iteration per second in percentage.
|
73
77
|
# @return [Float] +@ips_sd+ in percentage.
|
74
78
|
def error_percentage
|
75
|
-
|
79
|
+
@stats.error_percentage
|
76
80
|
end
|
77
81
|
|
78
82
|
alias_method :runtime, :seconds
|
@@ -84,7 +88,7 @@ module Benchmark
|
|
84
88
|
def body
|
85
89
|
case Benchmark::IPS.options[:format]
|
86
90
|
when :human
|
87
|
-
left = "%s (±%4.1f%%) i/s" % [Helpers.scale(@stats.central_tendency), error_percentage]
|
91
|
+
left = "%s (±%4.1f%%) i/s" % [Helpers.scale(@stats.central_tendency), @stats.error_percentage]
|
88
92
|
iters = Helpers.scale(@iterations)
|
89
93
|
|
90
94
|
if @show_total_time
|
@@ -93,7 +97,7 @@ module Benchmark
|
|
93
97
|
left.ljust(20) + (" - %s" % iters)
|
94
98
|
end
|
95
99
|
else
|
96
|
-
left = "%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, error_percentage]
|
100
|
+
left = "%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, @stats.error_percentage]
|
97
101
|
|
98
102
|
if @show_total_time
|
99
103
|
left.ljust(20) + (" - %10d in %10.6fs" % [@iterations, runtime])
|
data/lib/benchmark/ips/share.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'net/http'
|
2
4
|
require 'net/https'
|
3
5
|
require 'json'
|
@@ -5,7 +7,7 @@ require 'json'
|
|
5
7
|
module Benchmark
|
6
8
|
module IPS
|
7
9
|
class Share
|
8
|
-
DEFAULT_URL = "https://
|
10
|
+
DEFAULT_URL = "https://ips.fastruby.io"
|
9
11
|
def initialize(report, job)
|
10
12
|
@report = report
|
11
13
|
@job = job
|
@@ -3,27 +3,30 @@ module Benchmark
|
|
3
3
|
module Stats
|
4
4
|
|
5
5
|
class Bootstrap
|
6
|
-
|
7
|
-
attr_reader :data
|
6
|
+
include StatsMetric
|
7
|
+
attr_reader :data, :error, :samples
|
8
8
|
|
9
9
|
def initialize(samples, confidence)
|
10
10
|
dependencies
|
11
11
|
@iterations = 10_000
|
12
12
|
@confidence = (confidence / 100.0).to_s
|
13
|
+
@samples = samples
|
13
14
|
@data = Kalibera::Data.new({[0] => samples}, [1, samples.size])
|
14
15
|
interval = @data.bootstrap_confidence_interval(@iterations, @confidence)
|
15
16
|
@median = interval.median
|
16
17
|
@error = interval.error
|
17
18
|
end
|
18
19
|
|
20
|
+
# Average stat value
|
21
|
+
# @return [Float] central_tendency
|
19
22
|
def central_tendency
|
20
23
|
@median
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
# Determines how much slower this stat is than the baseline stat
|
27
|
+
# if this average is lower than the faster baseline, higher average is better (e.g. ips) (calculate accordingly)
|
28
|
+
# @param baseline [SD|Bootstrap] faster baseline
|
29
|
+
# @returns [Array<Float, nil>] the slowdown and the error (not calculated for standard deviation)
|
27
30
|
def slowdown(baseline)
|
28
31
|
low, slowdown, high = baseline.data.bootstrap_quotient(@data, @iterations, @confidence)
|
29
32
|
error = Timing.mean([slowdown - low, high - slowdown])
|
@@ -1,33 +1,41 @@
|
|
1
1
|
module Benchmark
|
2
2
|
module IPS
|
3
3
|
module Stats
|
4
|
-
|
4
|
+
|
5
5
|
class SD
|
6
|
-
|
6
|
+
include StatsMetric
|
7
|
+
attr_reader :error, :samples
|
8
|
+
|
7
9
|
def initialize(samples)
|
10
|
+
@samples = samples
|
8
11
|
@mean = Timing.mean(samples)
|
9
12
|
@error = Timing.stddev(samples, @mean).round
|
10
13
|
end
|
11
|
-
|
14
|
+
|
15
|
+
# Average stat value
|
16
|
+
# @return [Float] central_tendency
|
12
17
|
def central_tendency
|
13
18
|
@mean
|
14
19
|
end
|
15
|
-
|
16
|
-
def error
|
17
|
-
@error
|
18
|
-
end
|
19
20
|
|
21
|
+
# Determines how much slower this stat is than the baseline stat
|
22
|
+
# if this average is lower than the faster baseline, higher average is better (e.g. ips) (calculate accordingly)
|
23
|
+
# @param baseline [SD|Bootstrap] faster baseline
|
24
|
+
# @returns [Array<Float, nil>] the slowdown and the error (not calculated for standard deviation)
|
20
25
|
def slowdown(baseline)
|
21
|
-
|
22
|
-
|
26
|
+
if baseline.central_tendency > central_tendency
|
27
|
+
[baseline.central_tendency.to_f / central_tendency, 0]
|
28
|
+
else
|
29
|
+
[central_tendency.to_f / baseline.central_tendency, 0]
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
33
|
def footer
|
26
34
|
nil
|
27
35
|
end
|
28
|
-
|
36
|
+
|
29
37
|
end
|
30
|
-
|
38
|
+
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Benchmark
|
2
|
+
module IPS
|
3
|
+
module Stats
|
4
|
+
module StatsMetric
|
5
|
+
# Return entry's standard deviation of iteration per second in percentage.
|
6
|
+
# @return [Float] +@ips_sd+ in percentage.
|
7
|
+
def error_percentage
|
8
|
+
100.0 * (error.to_f / central_tendency)
|
9
|
+
end
|
10
|
+
|
11
|
+
def overlaps?(baseline)
|
12
|
+
baseline_low = baseline.central_tendency - baseline.error
|
13
|
+
baseline_high = baseline.central_tendency + baseline.error
|
14
|
+
my_high = central_tendency + error
|
15
|
+
my_low = central_tendency - error
|
16
|
+
my_high > baseline_low && my_low < baseline_high
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/test/test_benchmark_ips.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "minitest/autorun"
|
2
2
|
require "benchmark/ips"
|
3
3
|
require "stringio"
|
4
|
+
require "tmpdir"
|
4
5
|
|
5
6
|
class TestBenchmarkIPS < Minitest::Test
|
6
7
|
def setup
|
@@ -20,6 +21,19 @@ class TestBenchmarkIPS < Minitest::Test
|
|
20
21
|
assert $stdout.string.size > 0
|
21
22
|
end
|
22
23
|
|
24
|
+
def test_warmup0
|
25
|
+
$stdout = @old_stdout
|
26
|
+
|
27
|
+
out, err = capture_io do
|
28
|
+
Benchmark.ips(:time => 1, :warmup => 0, :quiet => false) do |x|
|
29
|
+
x.report("sleep 0.25") { sleep(0.25) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
refute_match(/Warming up -+/, out)
|
34
|
+
assert_empty err
|
35
|
+
end
|
36
|
+
|
23
37
|
def test_output
|
24
38
|
Benchmark.ips(1) do |x|
|
25
39
|
x.report("operation") { 100 * 100 }
|
@@ -40,6 +54,13 @@ class TestBenchmarkIPS < Minitest::Test
|
|
40
54
|
end
|
41
55
|
|
42
56
|
assert $stdout.string.size.zero?
|
57
|
+
|
58
|
+
Benchmark.ips do |x|
|
59
|
+
x.quiet = true
|
60
|
+
x.report("operation") { 100 * 100 }
|
61
|
+
end
|
62
|
+
|
63
|
+
assert $stdout.string.size.zero?
|
43
64
|
end
|
44
65
|
|
45
66
|
def test_ips
|
@@ -103,6 +124,21 @@ class TestBenchmarkIPS < Minitest::Test
|
|
103
124
|
assert_equal [:warming, :warmup_stats, :running, :add_report], suite.calls
|
104
125
|
end
|
105
126
|
|
127
|
+
def test_ips_config_suite_by_accsr
|
128
|
+
suite = Struct.new(:calls) do
|
129
|
+
def method_missing(method, *args)
|
130
|
+
calls << method
|
131
|
+
end
|
132
|
+
end.new([])
|
133
|
+
|
134
|
+
Benchmark.ips(0.1, 0.1) do |x|
|
135
|
+
x.suite = suite
|
136
|
+
x.report("job") {}
|
137
|
+
end
|
138
|
+
|
139
|
+
assert_equal [:warming, :warmup_stats, :running, :add_report], suite.calls
|
140
|
+
end
|
141
|
+
|
106
142
|
def test_ips_defaults
|
107
143
|
report = Benchmark.ips do |x|
|
108
144
|
x.report("sleep 0.25") { sleep(0.25) }
|
@@ -140,6 +176,17 @@ class TestBenchmarkIPS < Minitest::Test
|
|
140
176
|
assert all_data[0][:stddev]
|
141
177
|
end
|
142
178
|
|
179
|
+
def test_ips_empty
|
180
|
+
report = Benchmark.ips do |_x|
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
all_data = report.data
|
185
|
+
|
186
|
+
assert all_data
|
187
|
+
assert_equal [], all_data
|
188
|
+
end
|
189
|
+
|
143
190
|
def test_json_output
|
144
191
|
json_file = Tempfile.new("data.json")
|
145
192
|
|
@@ -158,4 +205,17 @@ class TestBenchmarkIPS < Minitest::Test
|
|
158
205
|
assert data[0]["ips"]
|
159
206
|
assert data[0]["stddev"]
|
160
207
|
end
|
208
|
+
|
209
|
+
def test_hold!
|
210
|
+
temp_file_name = Dir::Tmpname.create(["benchmark-ips", ".tmp"]) { }
|
211
|
+
|
212
|
+
Benchmark.ips(:time => 0.001, :warmup => 0.001) do |x|
|
213
|
+
x.report("operation") { 100 * 100 }
|
214
|
+
x.report("operation2") { 100 * 100 }
|
215
|
+
x.hold! temp_file_name
|
216
|
+
end
|
217
|
+
|
218
|
+
assert File.exist?(temp_file_name)
|
219
|
+
File.unlink(temp_file_name)
|
220
|
+
end
|
161
221
|
end
|
metadata
CHANGED
@@ -1,57 +1,63 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: benchmark-ips
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Phoenix
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: minitest
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5.
|
20
|
-
|
18
|
+
version: '5.14'
|
19
|
+
name: minitest
|
21
20
|
prerelease: false
|
21
|
+
type: :development
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5.
|
26
|
+
version: '5.14'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: rdoc
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
|
-
- - "
|
30
|
+
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
32
|
version: '4.0'
|
34
|
-
|
33
|
+
- - "<"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '7'
|
36
|
+
name: rdoc
|
35
37
|
prerelease: false
|
38
|
+
type: :development
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
40
43
|
version: '4.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '7'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
|
-
name: hoe
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
44
49
|
requirements:
|
45
50
|
- - "~>"
|
46
51
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
48
|
-
|
52
|
+
version: '3.22'
|
53
|
+
name: hoe
|
49
54
|
prerelease: false
|
55
|
+
type: :development
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
51
57
|
requirements:
|
52
58
|
- - "~>"
|
53
59
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
60
|
+
version: '3.22'
|
55
61
|
description: An iterations per second enhancement to Benchmark.
|
56
62
|
email:
|
57
63
|
- evan@phx.io
|
@@ -63,7 +69,6 @@ extra_rdoc_files:
|
|
63
69
|
- README.md
|
64
70
|
files:
|
65
71
|
- ".autotest"
|
66
|
-
- Gemfile.lock
|
67
72
|
- History.txt
|
68
73
|
- Manifest.txt
|
69
74
|
- README.md
|
@@ -77,13 +82,15 @@ files:
|
|
77
82
|
- lib/benchmark/ips/share.rb
|
78
83
|
- lib/benchmark/ips/stats/bootstrap.rb
|
79
84
|
- lib/benchmark/ips/stats/sd.rb
|
85
|
+
- lib/benchmark/ips/stats/stats_metric.rb
|
80
86
|
- lib/benchmark/timing.rb
|
81
87
|
- test/test_benchmark_ips.rb
|
82
88
|
homepage: https://github.com/evanphx/benchmark-ips
|
83
89
|
licenses:
|
84
90
|
- MIT
|
85
|
-
metadata:
|
86
|
-
|
91
|
+
metadata:
|
92
|
+
homepage_uri: https://github.com/evanphx/benchmark-ips
|
93
|
+
post_install_message:
|
87
94
|
rdoc_options:
|
88
95
|
- "--main"
|
89
96
|
- README.md
|
@@ -100,9 +107,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
107
|
- !ruby/object:Gem::Version
|
101
108
|
version: '0'
|
102
109
|
requirements: []
|
103
|
-
|
104
|
-
|
105
|
-
signing_key:
|
110
|
+
rubygems_version: 3.1.6
|
111
|
+
signing_key:
|
106
112
|
specification_version: 4
|
107
113
|
summary: An iterations per second enhancement to Benchmark.
|
108
114
|
test_files: []
|
data/Gemfile.lock
DELETED