benchmark-inputs 1.0.1 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5f1e62b1ab9bc9870a7d3dcc3f20e6260958ee63
4
- data.tar.gz: 0c602dc860cddbd4be045d6ad06821cebb0bbbd0
2
+ SHA256:
3
+ metadata.gz: a6e909d515239a8262e68c3f13b3cdb6d3840be4999b647aa1267dbd3184f69b
4
+ data.tar.gz: 37c78630c9af1f254438db2cb427e073a2bc97e2b25f7cedf4a0f73deba34d00
5
5
  SHA512:
6
- metadata.gz: bf99df51e67a7789e185d9c36ae715697e54e57fa7d81bc1d5bdb003eaf1e5028b2468812bfaac007053396fe4568429691e59d88a2ce9ef9cf971418d404e65
7
- data.tar.gz: 49e0985a2d32f963945532bc3d55f6db6eb4c22125ba47afb81903992c062a8d700adbb6fb5f57720d35f01c88a68574fee6c0b2e54638313397f4868af55e2a
6
+ metadata.gz: 184afc9a7110ad536dce0881db8c092958986e245e0a90af9040ac70372afac583811b29765c0098b5233e7e1c637c3c10188cce44e4e4c0d48930b96e5ccf8d
7
+ data.tar.gz: 9859889f725c6849d524d6fd08cb1a579c6598f1135b7000adc4cc77cb3910899a8700ee931f3679d36b36cbd11b7c19e08350b4c03b708dc8896b4f7af0c22e
@@ -0,0 +1,13 @@
1
+ ## 1.1.0
2
+
3
+ * Support `Benchmark.inputs` options hash
4
+
5
+
6
+ ## 1.0.1
7
+
8
+ * Fix stddev formatting
9
+
10
+
11
+ ## 1.0.0
12
+
13
+ * Initial release
data/README.md CHANGED
@@ -1,47 +1,51 @@
1
1
  # benchmark-inputs
2
2
 
3
- Input-focused benchmarking for Ruby. Given one or more blocks and a
4
- list of inputs to yield to them, benchmark-inputs will measure the speed
5
- (in invocations per second) of each block. Blocks which execute very
6
- quickly, as in microbenchmarks, are automatically invoked repeatedly to
7
- provide accurate measurements.
3
+ Input-focused benchmarking for Ruby. Given one or more blocks and an
4
+ array of inputs to yield to each of them, benchmark-inputs will measure
5
+ the speed (in invocations per second) of each block. Blocks which
6
+ execute very quickly, as in microbenchmarks, are automatically invoked
7
+ repeatedly to provide accurate measurements.
8
8
 
9
9
 
10
10
  ## Motivation
11
11
 
12
12
  I <3 [Fast Ruby][fast-ruby]. By extension, I <3 [benchmark-ips]. But,
13
- for certain usages, benchmark-ips doesn't let me write benchmarks the
14
- way I'd like. Consider the following example, *using benchmark-ips*:
13
+ for some use cases, benchmark-ips doesn't let me write benchmarks the
14
+ way I'd like. Consider the following example, *using benchmark-ips*:
15
15
 
16
16
  ```ruby
17
- require 'benchmark/ips' ### USING benchmark-ips (NOT benchmark-inputs)
17
+ require "benchmark/ips" ### USING benchmark-ips (NOT benchmark-inputs)
18
18
 
19
- STRINGS = ['abc', 'aaa', 'xyz', '']
19
+ STRINGS = ["abc", "aaa", "xyz", ""]
20
20
  Benchmark.ips do |job|
21
- job.report('String#tr'){ STRINGS.each{|s| s.tr('a', 'A') } }
22
- job.report('String#gsub'){ STRINGS.each{|s| s.gsub(/a/, 'A') } }
21
+ job.report("String#tr"){ STRINGS.each{|s| s.tr("a", "A") } }
22
+ job.report("String#gsub"){ STRINGS.each{|s| s.gsub(/a/, "A") } }
23
23
  job.compare!
24
24
  end
25
25
  ```
26
26
 
27
- The calls to `STRINGS.each` introduce performance overhead which skews
27
+ The calls to `STRINGS.each` introduce performance overhead that skews
28
28
  the time measurements. The less time the target function takes, the
29
- more relative overhead, and thus the more skew. For a microbenchmark
30
- this can be a problem. A possible workaround is to invoke the function
31
- on each value individually, but that is more verbose and error-prone:
29
+ more relative overhead, and thus more skew. For a microbenchmark this
30
+ can be a problem. A possible workaround is to invoke the function on
31
+ each value individually, but that is more verbose and more error-prone:
32
32
 
33
33
  ```ruby
34
- require 'benchmark/ips' ### USING benchmark-ips (NOT benchmark-inputs)
34
+ require "benchmark/ips" ### USING benchmark-ips (NOT benchmark-inputs)
35
35
 
36
- s1 = 'abc'; s2 = 'aaa'; s3 = 'xyz'; s4 = ''
36
+ s1, s2, s3, s4 = ["abc", "aaa", "xyz", ""]
37
37
  Benchmark.ips do |job|
38
- job.report('String#tr') do
39
- s1.tr('a', 'A'); s2.tr('a', 'A')
40
- s3.tr('a', 'A'); s4.tr('a', 'A')
38
+ job.report("String#tr") do
39
+ s1.tr("a", "A")
40
+ s2.tr("a", "A")
41
+ s3.tr("a", "A")
42
+ s4.tr("a", "A")
41
43
  end
42
- job.report('String#gsub') do
43
- s1.gsub(/a/, 'A'); s2.gsub(/a/, 'A')
44
- s3.gsub(/a/, 'A'); s4.gsub(/a/, 'A')
44
+ job.report("String#gsub") do
45
+ s1.gsub(/a/, "A")
46
+ s2.gsub(/a/, "A")
47
+ s3.gsub(/a/, "A")
48
+ s4.gsub(/a/, "A")
45
49
  end
46
50
  job.compare!
47
51
  end
@@ -51,11 +55,11 @@ end
51
55
  this gem: <a name="example1"></a>
52
56
 
53
57
  ```ruby
54
- require 'benchmark/inputs' ### USING benchmark-inputs
58
+ require "benchmark/inputs" ### USING benchmark-inputs
55
59
 
56
- Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
57
- job.report('String#tr'){|s| s.tr('a', 'A') }
58
- job.report('String#gsub'){|s| s.gsub(/a/, 'A') }
60
+ Benchmark.inputs(["abc", "aaa", "xyz", ""]) do |job|
61
+ job.report("String#tr"){|s| s.tr("a", "A") }
62
+ job.report("String#gsub"){|s| s.gsub(/a/, "A") }
59
63
  job.compare!
60
64
  end
61
65
  ```
@@ -80,18 +84,17 @@ Destructive operations also pose a challenge for microbenchmarks. Each
80
84
  invocation needs to operate on the same data, but `dup`ing the data
81
85
  introduces too much overhead and skew.
82
86
 
83
- benchmark-inputs' solution is to estimate the overhead incurred by
84
- `dup`, and exclude that from time measurements. Because the benchmark
85
- job already controls the input data, all of this can be handled with a
86
- single configuration line:
87
+ benchmark-inputs' solution is to estimate the overhead incurred by each
88
+ `dup`, and exclude that from the time measurements. Because the
89
+ benchmark job already controls the input data, everything can be handled
90
+ behind the scenes. To enable this, use the `dup_inputs` option:
87
91
 
88
92
  ```ruby
89
- require 'benchmark/inputs'
93
+ require "benchmark/inputs"
90
94
 
91
- Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
92
- job.dup_inputs = true # <--- single configuration line
93
- job.report('String#tr!'){|s| s.tr!('a', 'A') }
94
- job.report('String#gsub!'){|s| s.gsub!(/a/, 'A') }
95
+ Benchmark.inputs(["abc", "aaa", "xyz", ""], dup_inputs: true) do |job|
96
+ job.report("String#tr!"){|s| s.tr!("a", "A") }
97
+ job.report("String#gsub!"){|s| s.gsub!(/a/, "A") }
95
98
  job.compare!
96
99
  end
97
100
  ```
@@ -111,7 +114,7 @@ Comparison:
111
114
 
112
115
  That shows a slightly larger performance gap than the previous
113
116
  benchmark. This makes sense because the overhead of allocating new
114
- strings--previously via a non-bang method, but now via `dup`--is now
117
+ strings -- previously via a non-bang method, but now via `dup` -- is now
115
118
  excluded from the timings. Thus, the speed of `tr!` relative to `gsub!`
116
119
  is further emphasized.
117
120
 
@@ -129,13 +132,15 @@ problem.
129
132
 
130
133
  ## Installation
131
134
 
132
- $ gem install benchmark-inputs
135
+ ```bash
136
+ $ gem install benchmark-inputs
137
+ ```
133
138
 
134
139
 
135
140
  ## Usage
136
141
 
137
- See the [example above](#example1), or check the
138
- [documentation](http://www.rubydoc.info/gems/benchmark-inputs).
142
+ See the [example above](#example1), or check out the
143
+ [API documentation](http://www.rubydoc.info/gems/benchmark-inputs).
139
144
 
140
145
 
141
146
  ## License
@@ -146,4 +151,4 @@ See the [example above](#example1), or check the
146
151
 
147
152
 
148
153
  [fast-ruby]: https://github.com/JuanitoFatas/fast-ruby
149
- [benchmark-ips]: https://github.com/evanphx/benchmark-ips
154
+ [benchmark-ips]: https://rubygems.org/gems/benchmark-ips
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.12"
22
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rake", ">= 10.0"
23
23
  spec.add_development_dependency "minitest", "~> 5.0"
24
- spec.add_development_dependency "yard", "~> 0.8"
24
+ spec.add_development_dependency "yard", "~> 0.9"
25
25
  end
@@ -1,23 +1,45 @@
1
- require 'benchmark/inputs/version'
1
+ require "benchmark/inputs/version"
2
2
 
3
3
  module Benchmark
4
4
 
5
5
  # Initializes a benchmark job with the given inputs and yields that
6
6
  # job to the given block.
7
7
  #
8
- # Example:
9
- # Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
10
- # job.report('String#tr'){|s| s.tr('a', 'A') }
11
- # job.report('String#gsub'){|s| s.gsub(/a/, 'A') }
8
+ # @example Benchmarking non-destructive operations
9
+ # Benchmark.inputs(["abc", "aaa", "xyz", ""]) do |job|
10
+ # job.report("String#tr"){|s| s.tr("a", "A") }
11
+ # job.report("String#gsub"){|s| s.gsub(/a/, "A") }
12
12
  # job.compare!
13
13
  # end
14
14
  #
15
- # @param [Array] vals input values to yield to each benchmark action
16
- # @yield [job] configures job and runs benchmarks
17
- # @yieldparam [Benchmark::Inputs::Job] job benchmark runner
18
- # @return [Benchmark::Inputs::Job] benchmark runner
19
- def self.inputs(vals)
20
- job = Inputs::Job.new(vals)
15
+ # @example Benchmarking destructive operations
16
+ # Benchmark.inputs(["abc", "aaa", "xyz", ""], dup_inputs: true) do |job|
17
+ # job.report("String#tr!"){|s| s.tr!("a", "A") }
18
+ # job.report("String#gsub!"){|s| s.gsub!(/a/, "A") }
19
+ # job.compare!
20
+ # end
21
+ #
22
+ # @param values [Array]
23
+ # input values to yield to each benchmark action
24
+ # @param options [Hash]
25
+ # @option options :dup_inputs [Boolean]
26
+ # whether input values will be +dup+-ed before they are passed to a
27
+ # {Inputs::Job#report} block
28
+ # @option options :sample_n [Integer]
29
+ # number of samples to take when benchmarking
30
+ # @option options :sample_dt [Integer]
31
+ # approximate duration of time (in nanoseconds) each sample should
32
+ # take when benchmarking
33
+ # @yield [job]
34
+ # configures job and runs benchmarks
35
+ # @yieldparam job [Benchmark::Inputs::Job]
36
+ # benchmark runner
37
+ # @return [Benchmark::Inputs::Job]
38
+ # benchmark runner
39
+ # @raise [ArgumentError]
40
+ # if +values+ is empty
41
+ def self.inputs(values, **options)
42
+ job = Inputs::Job.new(values, options)
21
43
  yield job
22
44
  job
23
45
  end
@@ -29,46 +51,78 @@ module Benchmark
29
51
  NS_PER_MS = NS_PER_S / 1_000
30
52
 
31
53
  class Job
32
- attr_accessor :sample_n, :sample_dt
33
- attr_reader :dup_inputs, :reports
34
54
 
35
- def initialize(inputs)
55
+ # @param inputs [Array]
56
+ # input values to yield to each benchmark action
57
+ # @param dup_inputs [Boolean]
58
+ # whether input values will be +dup+-ed before they are passed
59
+ # to a {report} block
60
+ # @param sample_n [Integer]
61
+ # number of samples to take when benchmarking
62
+ # @param sample_dt [Integer]
63
+ # approximate duration of time (in nanoseconds) each sample
64
+ # should take when benchmarking
65
+ # @raise [ArgumentError]
66
+ # if +inputs+ is empty
67
+ def initialize(inputs, dup_inputs: false, sample_n: 10, sample_dt: NS_PER_MS * 200)
68
+ raise ArgumentError, "No inputs specified" if inputs.empty?
69
+
36
70
  @inputs = inputs
37
- @dup_inputs = false
38
- @sample_n = 10
39
- @sample_dt = NS_PER_MS * 200
71
+ @dup_inputs = dup_inputs
72
+ @sample_n = sample_n
73
+ @sample_dt = sample_dt
40
74
  @reports = []
41
75
  def_bench!
42
76
  end
43
77
 
44
- # Sets the +dup_inputs+ flag. If set to true, causes input values
45
- # to be +dup+'d before they are passed to a +report+ block. This
46
- # is necessary when +report+ blocks destructively modify their
78
+ # Indicates whether input values will be +dup+-ed before they are
79
+ # passed to a {report} block. Defaults to +false+. This should
80
+ # be set to +true+ if {report} blocks destructively modify their
47
81
  # arguments.
48
82
  #
49
- # Example:
50
- # Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
51
- # job.dup_inputs = true # <---
52
- # job.report('String#tr!'){|s| s.tr!('a', 'A') }
53
- # job.report('String#gsub!'){|s| s.gsub!(/a/, 'A') }
54
- # job.compare!
55
- # end
83
+ # @return [Boolean]
84
+ attr_reader :dup_inputs
85
+
86
+ # See {dup_inputs}.
56
87
  #
57
- # @param [Boolean] val value to set
58
- def dup_inputs=(val)
59
- @dup_inputs = val
88
+ # @param flag [Boolean]
89
+ # @return [Boolean]
90
+ def dup_inputs=(flag)
91
+ @dup_inputs = flag
60
92
  def_bench!
61
93
  @dup_inputs
62
94
  end
63
95
 
64
- # Benchmarks the given block using the initially provided input
65
- # values. If +#dup_inputs+ is set to true, each input value is
66
- # +dup+'d before being passed to the block. Afterwards, the
67
- # block's invocations per second (i/s) is printed to +$stdout+.
96
+ # The number of samples to take when benchmarking. Defaults to 10.
97
+ #
98
+ # @return [Integer]
99
+ attr_accessor :sample_n
100
+
101
+ # The approximate duration of time (in nanoseconds) each sample
102
+ # should take when benchmarking. Defaults to 200,000 nanoseconds.
103
+ #
104
+ # @return [Integer]
105
+ attr_accessor :sample_dt
106
+
107
+ # Array of benchmark reports. Each call to {report} adds an
108
+ # element to this array.
68
109
  #
69
- # @param [String] label label for the benchmark
70
- # @yield [input] action to benchmark
71
- # @yieldparam input one of the initially provided input values
110
+ # @return [Array<Benchmark::Inputs::Report>]
111
+ attr_reader :reports
112
+
113
+ # Benchmarks the given block using the previously provided input
114
+ # values. If {dup_inputs} is set to true, each input value is
115
+ # +dup+'d before being passed to the block. The block's
116
+ # iterations per second (i/s) is printed to +$stdout+, and a
117
+ # {Report} is added to {reports}.
118
+ #
119
+ # @param label [String]
120
+ # label for the benchmark
121
+ # @yield [input]
122
+ # action to benchmark
123
+ # @yieldparam input [Object]
124
+ # one of the initially provided input values
125
+ # @return [void]
72
126
  def report(label)
73
127
  # estimate repititions
74
128
  reps = 1
@@ -91,13 +145,14 @@ module Benchmark
91
145
  $stdout.puts(r.label)
92
146
  $stdout.printf(" %.1f i/s (\u00B1%.2f%%)\n", r.ips, r.stddev / r.ips * 100)
93
147
  @reports << r
94
- r
95
148
  end
96
149
 
97
150
  # Prints the relative speeds (from fastest to slowest) of all
98
- # +#report+ed benchmarks to +$stdout+.
151
+ # {report}-ed benchmarks to +$stdout+.
152
+ #
153
+ # @return [void]
99
154
  def compare!
100
- return $stdout.puts('Nothing to compare!') if @reports.empty?
155
+ return $stdout.puts("Nothing to compare!") if @reports.empty?
101
156
 
102
157
  @reports.sort_by!{|r| -r.ips }
103
158
  @reports.each{|r| r.slower_than!(@reports.first) }
@@ -109,9 +164,9 @@ module Benchmark
109
164
  @reports.each_with_index do |r, i|
110
165
  $stdout.printf(format, r.label, r.ips)
111
166
  if r.ratio
112
- $stdout.printf(' - %.2fx slower', r.ratio)
167
+ $stdout.printf(" - %.2fx slower", r.ratio)
113
168
  elsif i > 0
114
- $stdout.printf(' - same-ish: difference falls within error')
169
+ $stdout.printf(" - same-ish: difference falls within error")
115
170
  end
116
171
  $stdout.puts
117
172
  end
@@ -123,12 +178,11 @@ module Benchmark
123
178
  def def_bench!
124
179
  assigns = @inputs.each_index.map do |i|
125
180
  "x#{i} = @inputs[#{i}]"
126
- end.join(';')
181
+ end.join(";")
127
182
 
128
- yields = @inputs.each_with_index.map do |x, i|
129
- dup = (@dup_inputs && x.respond_to?(:dup)) ? '.dup' : ''
130
- "yield(x#{i}#{dup})"
131
- end.join(';')
183
+ yields = @inputs.each_index.map do |i|
184
+ dup_inputs ? "yield(x#{i}.dup)" : "yield(x#{i})"
185
+ end.join(";")
132
186
 
133
187
  code = <<-CODE
134
188
  def bench(reps)
@@ -151,8 +205,21 @@ module Benchmark
151
205
 
152
206
 
153
207
  class Report
154
- attr_reader :label, :ratio
208
+ # The label for the report.
209
+ #
210
+ # @return [String]
211
+ attr_reader :label
212
+
213
+ # The ratio of iterations per second for this report compared to
214
+ # the fastest report. Will be +nil+ if the difference between the
215
+ # two falls within the combined measurement error.
216
+ #
217
+ # This value is set by {Benchmark::Inputs::Job#compare!}.
218
+ #
219
+ # @return [Float, nil]
220
+ attr_reader :ratio
155
221
 
222
+ # @!visibility private
156
223
  def initialize(label, invocs_per_sample)
157
224
  @label = label.to_s
158
225
  @invocs_per_sample = invocs_per_sample.to_f
@@ -163,6 +230,7 @@ module Benchmark
163
230
  @m2 = 0.0
164
231
  end
165
232
 
233
+ # @!visibility private
166
234
  def add_sample(time_ns)
167
235
  sample_ips = @invocs_per_sample * NS_PER_S / time_ns
168
236
 
@@ -175,18 +243,26 @@ module Benchmark
175
243
  @stddev = nil
176
244
  end
177
245
 
246
+ # The estimated iterations per second for the report.
247
+ #
248
+ # @return [Float]
178
249
  def ips
179
250
  @mean
180
251
  end
181
252
 
253
+ # The {ips} standard deviation.
254
+ #
255
+ # @return [Float]
182
256
  def stddev
183
257
  @stddev ||= @n < 2 ? 0.0 : Math.sqrt(@m2 / (@n - 1))
184
258
  end
185
259
 
260
+ # @!visibility private
186
261
  def slower_than!(faster)
187
262
  @ratio = overlap?(faster) ? nil : (faster.ips / self.ips)
188
263
  end
189
264
 
265
+ # @!visibility private
190
266
  def overlap?(faster)
191
267
  (faster.ips - faster.stddev) <= (self.ips + self.stddev)
192
268
  end
@@ -1,5 +1,5 @@
1
1
  module Benchmark
2
2
  module Inputs
3
- VERSION = "1.0.1"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchmark-inputs
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Hefner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-07-05 00:00:00.000000000 Z
11
+ date: 2019-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,14 +28,14 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.8'
61
+ version: '0.9'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.8'
68
+ version: '0.9'
69
69
  description:
70
70
  email:
71
71
  - jonathan.hefner@gmail.com
@@ -75,6 +75,7 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
77
  - ".travis.yml"
78
+ - CHANGELOG.md
78
79
  - Gemfile
79
80
  - LICENSE.txt
80
81
  - README.md
@@ -101,10 +102,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
104
  requirements: []
104
- rubyforge_project:
105
- rubygems_version: 2.4.8
105
+ rubygems_version: 3.0.1
106
106
  signing_key:
107
107
  specification_version: 4
108
108
  summary: Input-focused benchmarking
109
109
  test_files: []
110
- has_rdoc: