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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: feceba6a7a761d8eecc66d7a4e66e3458d0cd5fa
4
- data.tar.gz: 5c905cacd3d45fa76abf30ac2695745f1c3a23ac
2
+ SHA256:
3
+ metadata.gz: 5bec7b23527f8a5f331788f9de05073eede35e26bbc5a6b81c05a609d964c0ff
4
+ data.tar.gz: aa8ba81457df018d94262d0eb722870d34406204d02a61b0ad1aa398fa2903ff
5
5
  SHA512:
6
- metadata.gz: 5d5bb801ae291bab7c026f0495d460259aa5fdde347ae776630f0c8f1fe24f994da1222005c87d1cd1f4ac130edf8438c8e92a5e46d94b7a6a47022ca2fb2275
7
- data.tar.gz: 13c64a8eb5e8aac398b3b6f258a323907e643cd9c45e1bfd6438b72fedfe55c798d6ee32489a480135ce92d919c453907bee9f468c977a9903cd06cfefc40211
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 quickly your benchmark result with others. Run you benchmark
184
- with `SHARE=1` argument. I.e.: `SHARE=1 ruby my_benchmark.rb`.
185
- Result will be sent to [benchmark.fyi](https://benchmark.fyi/) and benchmark-ips
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
@@ -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
- best_low = best.stats.central_tendency - best.stats.error
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.7.2"
21
+ VERSION = "2.9.0"
19
22
 
20
23
  # CODENAME of current version.
21
- CODENAME = "Cultivating Confidence"
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
- if defined? Benchmark::Suite and Suite.current
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 if job.hold? && job.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
@@ -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
- @compare = false
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
- # Set @compare to true.
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
- # Set @hold to true.
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
- # Set @json_path to given path, defaults to "data.json".
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 = Hash[File.open(@held_path).map { |line|
177
- result = JSON.parse(line)
178
- [result['item'], result]
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
- @stdout.start_warming if @stdout
184
- @iterations.times do
185
- run_warmup
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 if @stdout
189
-
190
- held = nil
191
-
249
+
250
+ @stdout.start_running
251
+
192
252
  @iterations.times do |n|
193
- held = run_benchmark
253
+ run_benchmark
194
254
  end
195
255
 
196
- @stdout.footer if @stdout
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 hold? && @held_results && @held_results.key?(item.label)
208
-
209
- @suite.warming item.label, @warmup if @suite
210
- @stdout.warming item.label, @warmup if @stdout
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
- warmup_iter = 0
218
-
219
- while Timing.now < target
220
- item.call_times(1)
221
- warmup_iter += 1
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
- after = Timing.now
292
+ cycles = cycles_per_100ms warmup_time_us, warmup_iter
293
+ @timing[item] = cycles
225
294
 
226
- warmup_time_us = Timing.time_us(before, after)
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] = cycles_per_100ms warmup_time_us, warmup_iter
301
+ @stdout.warmup_stats warmup_time_us, @timing[item]
302
+ @suite.warmup_stats warmup_time_us, @timing[item]
229
303
 
230
- @stdout.warmup_stats warmup_time_us, @timing[item] if @stdout
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 hold? && @held_results && @held_results.key?(item.label)
241
- result = @held_results[item.label]
242
- create_report(item.label, result['measured_us'], result['iter'],
243
- create_stats(result['samples']), result['cycles'])
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 if @stdout
290
- @suite.add_report rep, caller(1).first if @suite
291
-
292
- if hold? && item != @list.last
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
- compile action
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
- @call_loop = true
28
+ compile_block_with_manual_loop
27
29
  else
28
- @call_loop = false
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, return if +@call_loop+ is present.
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
- return @action.call(times) if @call_loop
47
+ raise '#call_times should be redefined per Benchmark::IPS::Job::Entry instance'
48
+ end
48
49
 
49
- act = @action
50
+ def compile_block
51
+ m = (class << self; self; end)
52
+ code = <<-CODE
53
+ def call_times(times)
54
+ act = @action
50
55
 
51
- i = 0
52
- while i < times
53
- act.call
54
- i += 1
55
- end
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 compile(str)
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
@@ -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
- 100.0 * (@stats.error.to_f / @stats.central_tendency)
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])
@@ -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://benchmark.fyi"
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
- def error
24
- @error
25
- end
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
- slowdown = baseline.central_tendency.to_f / central_tendency
22
- [slowdown, nil]
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
@@ -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.7.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: 2016-08-18 00:00:00.000000000 Z
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.9'
20
- type: :development
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.9'
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
- type: :development
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.15'
48
- type: :development
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.15'
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
- post_install_message:
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
- rubyforge_project:
104
- rubygems_version: 2.5.1
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
@@ -1,18 +0,0 @@
1
- GEM
2
- remote: https://rubygems.org/
3
- specs:
4
- hoe (3.15.0)
5
- rake (>= 0.8, < 12.0)
6
- minitest (5.8.4)
7
- rake (10.5.0)
8
-
9
- PLATFORMS
10
- ruby
11
-
12
- DEPENDENCIES
13
- hoe (~> 3.14)
14
- minitest
15
- rake (~> 10.5)
16
-
17
- BUNDLED WITH
18
- 1.11.2