benchmark-ips 2.7.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](http://badge.fury.io/rb/benchmark-ips)
|
2
7
|
[](http://travis-ci.org/evanphx/benchmark-ips)
|
3
8
|
[](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