benchmark-ips 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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