benchmark-ips 1.0.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.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2012-03-23
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/benchmark_ips
7
+ lib/benchmark/compare.rb
8
+ lib/benchmark/helpers.rb
9
+ lib/benchmark/ips.rb
10
+ lib/benchmark/timing.rb
11
+ test/test_benchmark_ips.rb
data/README.txt ADDED
@@ -0,0 +1,80 @@
1
+ = benchmark-ips
2
+
3
+ * http://github.com/evanphx/benchmark-ips
4
+
5
+ == DESCRIPTION:
6
+
7
+ A iterations per second enhancement to Benchmark
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * benchmark/ips - benchmarks a blocks iterations/second. For short snippits
12
+ of code, ips automatically figures out how many times to run the code
13
+ to get interesting data. No more guessing at random iteration counts!
14
+
15
+ == SYNOPSIS:
16
+
17
+ require 'benchmark/ips'
18
+
19
+ Benchmark.ips do
20
+ # Typical mode, runs the block as many times as it can
21
+ x.report("addition") { 1 + 2 }
22
+
23
+ # To reduce overhead, the number of iterations is passed in
24
+ # and the block must run the code the specific number of times.
25
+ # Used for when the workload is very small and any overhead
26
+ # introduces incorrectable errors.
27
+ x.report("addition2") do |times|
28
+ i = 0
29
+ while i < times
30
+ 1 + 2
31
+ end
32
+ end
33
+
34
+ # To reduce overhead even more, grafts the code given into
35
+ # the loop that performs the iterations internally to reduce
36
+ # overhead. Typically not needed, use the |times| form instead.
37
+ x.report("addition3", "1 + 2")
38
+ end
39
+
40
+ == REQUIREMENTS:
41
+
42
+ * None!
43
+
44
+ == INSTALL:
45
+
46
+ * gem install benchmark-ips
47
+
48
+ == DEVELOPERS:
49
+
50
+ After checking out the source, run:
51
+
52
+ $ rake newb
53
+
54
+ This task will install any missing dependencies, run the tests/specs,
55
+ and generate the RDoc.
56
+
57
+ == LICENSE:
58
+
59
+ (The MIT License)
60
+
61
+ Copyright (c) 2012 Evan Phoenix
62
+
63
+ Permission is hereby granted, free of charge, to any person obtaining
64
+ a copy of this software and associated documentation files (the
65
+ 'Software'), to deal in the Software without restriction, including
66
+ without limitation the rights to use, copy, modify, merge, publish,
67
+ distribute, sublicense, and/or sell copies of the Software, and to
68
+ permit persons to whom the Software is furnished to do so, subject to
69
+ the following conditions:
70
+
71
+ The above copyright notice and this permission notice shall be
72
+ included in all copies or substantial portions of the Software.
73
+
74
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
75
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
76
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
77
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
78
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
79
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
80
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'benchmark-ips' do
7
+ developer('Evan Phoenix', 'evan@phx.io')
8
+ end
9
+
10
+ # vim: syntax=ruby
data/bin/benchmark_ips ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ abort "you need to write me"
@@ -0,0 +1,41 @@
1
+ module Benchmark
2
+ def compare(*reports)
3
+ return if reports.size < 2
4
+
5
+ iter = false
6
+ sorted = reports.sort do |a,b|
7
+ if a.respond_to? :ips
8
+ iter = true
9
+ b.ips <=> a.ips
10
+ else
11
+ a.runtime <=> b.runtime
12
+ end
13
+ end
14
+
15
+ best = sorted.shift
16
+
17
+ STDOUT.puts "\nComparison:"
18
+
19
+ if iter
20
+ STDOUT.printf "%20s: %10.1f i/s\n", best.label, best.ips
21
+ else
22
+ STDOUT.puts "#{best.rjust(20)}: #{best.runtime}s"
23
+ end
24
+
25
+ sorted.each do |report|
26
+ name = report.label
27
+
28
+ if iter
29
+ x = (best.ips.to_f / report.ips.to_f)
30
+ STDOUT.printf "%20s: %10.1f i/s - %.2fx slower\n", name, report.ips, x
31
+ else
32
+ x = "%.2f" % (report.ips.to_f / best.ips.to_f)
33
+ STDOUT.puts "#{name.rjust(20)}: #{report.runtime}s - #{x}x slower"
34
+ end
35
+ end
36
+
37
+ STDOUT.puts
38
+ end
39
+
40
+ module_function :compare
41
+ end
@@ -0,0 +1,46 @@
1
+ module Benchmark
2
+ module Helpers
3
+
4
+ def fixnum_max
5
+ if Object.const_defined?(:RUBY_ENGINE)
6
+ case RUBY_ENGINE
7
+ when "ruby"
8
+ 2 ** (wordsize - 2) - 1
9
+ when "rbx"
10
+ Fixnum::MAX
11
+ when "jruby"
12
+ 9223372036854775807
13
+ else
14
+ raise "Maximum Fixnum size now known yet for #{RUBY_ENGINE}"
15
+ end
16
+ else
17
+ 2 ** (wordsize - 2) - 1
18
+ end
19
+ end
20
+ module_function :fixnum_max
21
+
22
+ def fixnum_min
23
+ if Object.const_defined?(:RUBY_ENGINE)
24
+ case RUBY_ENGINE
25
+ when "ruby"
26
+ - 2 ** (wordsize - 2)
27
+ when "rbx"
28
+ Fixnum::MIN
29
+ when "jruby"
30
+ -9223372036854775808
31
+ else
32
+ raise "Maximum Fixnum size now known yet for #{RUBY_ENGINE}"
33
+ end
34
+ else
35
+ - 2 ** (wordsize - 2)
36
+ end
37
+ end
38
+ module_function :fixnum_min
39
+
40
+ def wordsize
41
+ 8 * 1.size
42
+ end
43
+ module_function :wordsize
44
+
45
+ end
46
+ end
@@ -0,0 +1,238 @@
1
+ # encoding: utf-8
2
+ require 'benchmark/timing'
3
+ require 'benchmark/compare'
4
+
5
+ module Benchmark
6
+
7
+ class IPSReport
8
+ VERSION = "1.0.0"
9
+
10
+ def initialize(label, us, iters, ips, ips_sd, cycles)
11
+ @label = label
12
+ @microseconds = us
13
+ @iterations = iters
14
+ @ips = ips
15
+ @ips_sd = ips_sd
16
+ @measurement_cycle = cycles
17
+ end
18
+
19
+ attr_reader :label, :microseconds, :iterations, :ips, :ips_sd, :measurement_cycle
20
+
21
+ def seconds
22
+ @microseconds.to_f / 1_000_000.0
23
+ end
24
+
25
+ def stddev_percentage
26
+ 100.0 * (@ips_sd.to_f / @ips.to_f)
27
+ end
28
+
29
+ alias_method :runtime, :seconds
30
+
31
+ def body
32
+ left = "%10.1f (±%.1f%%) i/s" % [ips, stddev_percentage]
33
+ left.ljust(20) + (" - %10d in %10.6fs (cycle=%d)" %
34
+ [@iterations, runtime, @measurement_cycle])
35
+ end
36
+
37
+ def header
38
+ @label.rjust(20)
39
+ end
40
+
41
+ def to_s
42
+ "#{header} #{body}"
43
+ end
44
+
45
+ def display
46
+ $stdout.puts to_s
47
+ end
48
+ end
49
+
50
+ class IPSJob
51
+ class Entry
52
+ def initialize(label, action)
53
+ @label = label
54
+
55
+ if action.kind_of? String
56
+ compile action
57
+ @action = self
58
+ @as_action = true
59
+ else
60
+ unless action.respond_to? :call
61
+ raise ArgumentError, "invalid action, must respond to #call"
62
+ end
63
+
64
+ @action = action
65
+
66
+ if action.respond_to? :arity and action.arity > 0
67
+ @call_loop = true
68
+ else
69
+ @call_loop = false
70
+ end
71
+
72
+ @as_action = false
73
+ end
74
+ end
75
+
76
+ attr_reader :label, :action
77
+
78
+ def as_action?
79
+ @as_action
80
+ end
81
+
82
+ def call_times(times)
83
+ return @action.call(times) if @call_loop
84
+
85
+ act = @action
86
+
87
+ i = 0
88
+ while i < times
89
+ act.call
90
+ i += 1
91
+ end
92
+ end
93
+
94
+ def compile(str)
95
+ m = (class << self; self; end)
96
+ code = <<-CODE
97
+ def call_times(__total);
98
+ __i = 0
99
+ while __i < __total
100
+ #{str};
101
+ __i += 1
102
+ end
103
+ end
104
+ CODE
105
+ m.class_eval code
106
+ end
107
+ end
108
+
109
+ def initialize
110
+ @list = []
111
+ @compare = false
112
+ end
113
+
114
+ attr_reader :compare
115
+
116
+ def compare!
117
+ @compare = true
118
+ end
119
+
120
+ #
121
+ # Registers the given label and block pair in the job list.
122
+ #
123
+ def item(label="", str=nil, &blk) # :yield:
124
+ if blk and str
125
+ raise ArgmentError, "specify a block and a str, but not both"
126
+ end
127
+
128
+ action = str || blk
129
+ raise ArgmentError, "no block or string" unless action
130
+
131
+ @list.push Entry.new(label, action)
132
+ self
133
+ end
134
+
135
+ alias_method :report, :item
136
+
137
+ # An array of 2-element arrays, consisting of label and block pairs.
138
+ attr_reader :list
139
+ end
140
+
141
+ def ips(time=5, warmup=2)
142
+ suite = nil
143
+
144
+ sync, $stdout.sync = $stdout.sync, true
145
+
146
+ if defined? Benchmark::Suite and Suite.current
147
+ suite = Benchmark::Suite.current
148
+ end
149
+
150
+ job = IPSJob.new
151
+ yield job
152
+
153
+ reports = []
154
+
155
+ job.list.each do |item|
156
+ suite.warming item.label, warmup if suite
157
+
158
+ Timing.clean_env
159
+
160
+ if !suite or !suite.quiet?
161
+ if item.label.size > 20
162
+ $stdout.print "#{item.label}\n#{' ' * 20}"
163
+ else
164
+ $stdout.print item.label.rjust(20)
165
+ end
166
+ end
167
+
168
+ before = Time.now
169
+ target = Time.now + warmup
170
+
171
+ warmup_iter = 0
172
+
173
+ while Time.now < target
174
+ item.call_times(1)
175
+ warmup_iter += 1
176
+ end
177
+
178
+ after = Time.now
179
+
180
+ warmup_time = (after.to_f - before.to_f) * 1_000_000.0
181
+
182
+ # calculate the time to run approx 100ms
183
+
184
+ cycles_per_100ms = ((100_000 / warmup_time) * warmup_iter).to_i
185
+ cycles_per_100ms = 1 if cycles_per_100ms <= 0
186
+
187
+ suite.warmup_stats warmup_time, cycles_per_100ms if suite
188
+
189
+ Timing.clean_env
190
+
191
+ suite.running item.label, time if suite
192
+
193
+ iter = 0
194
+
195
+ target = Time.now + time
196
+
197
+ measurements = []
198
+
199
+ while Time.now < target
200
+ before = Time.now
201
+ item.call_times cycles_per_100ms
202
+ after = Time.now
203
+
204
+ iter += cycles_per_100ms
205
+
206
+ measurements << ((after.to_f - before.to_f) * 1_000_000.0)
207
+ end
208
+
209
+ measured_us = measurements.inject(0) { |a,i| a + i }
210
+
211
+ seconds = measured_us.to_f / 1_000_000.0
212
+
213
+ all_ips = measurements.map { |i| cycles_per_100ms.to_f / (i.to_f / 1_000_000) }
214
+
215
+ avg_ips = Timing.mean(all_ips)
216
+ sd_ips = Timing.stddev(all_ips).round
217
+
218
+ rep = IPSReport.new(item.label, measured_us, iter, avg_ips, sd_ips, cycles_per_100ms)
219
+
220
+ $stdout.puts " #{rep.body}" if !suite or !suite.quiet?
221
+
222
+ suite.add_report rep, caller(1).first if suite
223
+
224
+ $stdout.sync = sync
225
+
226
+ reports << rep
227
+ end
228
+
229
+ if job.compare
230
+ Benchmark.compare(*reports)
231
+ end
232
+
233
+ return reports
234
+ end
235
+
236
+
237
+ module_function :ips
238
+ end
@@ -0,0 +1,61 @@
1
+ module Benchmark
2
+ module Timing
3
+ def self.resolution
4
+ samples = []
5
+
6
+ t1 = TimeVal.new
7
+ t2 = TimeVal.new
8
+
9
+ 30.times do
10
+ t1.update!
11
+
12
+ while true
13
+ t2.update!
14
+ break if t2 != t1
15
+ end
16
+
17
+ samples << t1.diff(t2)
18
+ end
19
+
20
+ sum = samples.inject(0) { |acc, i| acc + i }
21
+ sum / 30
22
+ end
23
+
24
+ def self.mean(samples)
25
+ sum = samples.inject(0) { |acc, i| acc + i }
26
+ sum / samples.size
27
+ end
28
+
29
+ def self.variance(samples, m=nil)
30
+ m ||= mean(samples)
31
+
32
+ total = samples.inject(0) { |acc, i| acc + ((i - m) ** 2) }
33
+
34
+ total / samples.size
35
+ end
36
+
37
+ def self.stddev(samples, m=nil)
38
+ Math.sqrt variance(samples, m)
39
+ end
40
+
41
+ def self.resample_mean(samples, resample_times=100)
42
+ resamples = []
43
+
44
+ resample_times.times do
45
+ resample = samples.map { samples[rand(samples.size)] }
46
+ resamples << Timing.mean(resample)
47
+ end
48
+
49
+ resamples
50
+ end
51
+
52
+ def self.clean_env
53
+ # rbx
54
+ if GC.respond_to? :run
55
+ GC.run(true)
56
+ else
57
+ GC.start
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ require "test/unit"
2
+ require "benchmark/ips"
3
+ require "stringio"
4
+
5
+ class TestBenchmarkIPS < Test::Unit::TestCase
6
+ def setup
7
+ @old_stdout = $stdout
8
+ $stdout = StringIO.new
9
+ end
10
+
11
+ def teardown
12
+ $stdout = @old_stdout
13
+ end
14
+
15
+ def test_ips
16
+ reports = Benchmark.ips(1,1) do |x|
17
+ x.report("sleep") { sleep(0.25) }
18
+ end
19
+
20
+ rep = reports.first
21
+
22
+ assert_equal "sleep", rep.label
23
+ assert_equal 4, rep.iterations
24
+ assert_in_delta 4.0, rep.ips, 0.2
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: benchmark-ips
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Evan Phoenix
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-24 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rdoc
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 19
29
+ segments:
30
+ - 3
31
+ - 10
32
+ version: "3.10"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 31
44
+ segments:
45
+ - 2
46
+ - 14
47
+ version: "2.14"
48
+ type: :development
49
+ version_requirements: *id002
50
+ description: A iterations per second enhancement to Benchmark
51
+ email:
52
+ - evan@phx.io
53
+ executables:
54
+ - benchmark_ips
55
+ extensions: []
56
+
57
+ extra_rdoc_files:
58
+ - History.txt
59
+ - Manifest.txt
60
+ - README.txt
61
+ files:
62
+ - .autotest
63
+ - History.txt
64
+ - Manifest.txt
65
+ - README.txt
66
+ - Rakefile
67
+ - bin/benchmark_ips
68
+ - lib/benchmark/compare.rb
69
+ - lib/benchmark/helpers.rb
70
+ - lib/benchmark/ips.rb
71
+ - lib/benchmark/timing.rb
72
+ - test/test_benchmark_ips.rb
73
+ - .gemtest
74
+ homepage: http://github.com/evanphx/benchmark-ips
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --main
80
+ - README.txt
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ requirements: []
102
+
103
+ rubyforge_project: benchmark-ips
104
+ rubygems_version: 1.8.18
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: A iterations per second enhancement to Benchmark
108
+ test_files:
109
+ - test/test_benchmark_ips.rb