benchmark-inputs 1.0.1 → 1.1.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: 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: