bnchmrkr 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.reek +24 -1
- data/.travis.yml +1 -1
- data/Gemfile +1 -2
- data/README.md +75 -18
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/lib/bnchmrkr.rb +89 -129
- data/lib/bnchmrkr/mark.rb +95 -0
- data/test/examples/test_examples.rb +4 -2
- data/test/unit/test_contrived.rb +42 -31
- data/test/unit/test_exceptions.rb +9 -5
- data/test/unit/test_initialize.rb +13 -2
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f9a56d97f5184f42f18cd757cd07e102a6858a8
|
4
|
+
data.tar.gz: eb6d17fe537b8e9a1ab56a5f2999e6d49572acc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8f9736fa5621e2663b6c84ba286c501bedf312eb9ec1bb06af97844c07ac580152672747733f7c04947674e37a52942e40d3b71aed53c2ea2d36b52aa8d78ed
|
7
|
+
data.tar.gz: 84dac7131ef08ec8512c57f6fc476558ddb5c20d947ebe960ea99011f8294095af90e9a43be3afd767a9ac2488062da9d3f1891024c65fc3239a2754bdb1c3eb
|
data/.reek
CHANGED
@@ -10,19 +10,42 @@ BooleanParameter:
|
|
10
10
|
ControlParameter:
|
11
11
|
enabled: false
|
12
12
|
|
13
|
+
DataClump:
|
14
|
+
min_clump_size: 4
|
15
|
+
|
13
16
|
DuplicateMethodCall:
|
14
17
|
enabled: false
|
15
18
|
|
19
|
+
# this is not a real thing
|
20
|
+
FeatureEnvy:
|
21
|
+
enabled: false
|
22
|
+
|
23
|
+
# this should have understanding of non-required parameters
|
24
|
+
LongParameterList:
|
25
|
+
max_params: 5
|
26
|
+
|
16
27
|
NestedIterators:
|
17
28
|
max_allowed_nesting: 3
|
18
29
|
|
30
|
+
# you don't know my life
|
31
|
+
NilCheck:
|
32
|
+
enabled: false
|
33
|
+
|
19
34
|
# more lacking context judgement - when _would_ you be ok with '!' ?
|
20
35
|
PrimaDonnaMethod:
|
21
36
|
enabled: false
|
22
37
|
|
23
38
|
# this feels a bit arbitrary.. maybe would make more sense if was an average
|
24
39
|
TooManyStatements:
|
25
|
-
max_statements:
|
40
|
+
max_statements: 30
|
41
|
+
|
42
|
+
# see above
|
43
|
+
TooManyInstanceVariables:
|
44
|
+
max_instance_variables: 20
|
45
|
+
|
46
|
+
# see above
|
47
|
+
TooManyMethods:
|
48
|
+
max_methods: 20
|
26
49
|
|
27
50
|
# i don't name my exceptions specifically. that doesn't make me a bad person.
|
28
51
|
UncommunicativeVariableName:
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,9 +2,8 @@ ruby '2.0.0'
|
|
2
2
|
|
3
3
|
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem 'rake', '~> 10.5.0', '>= 10.5.0'
|
6
|
-
|
7
5
|
group :development do
|
6
|
+
gem 'rake', '~> 10.5.0', '>= 10.5.0'
|
8
7
|
gem 'jeweler', '~> 2.0.1', '>= 2.0.1'
|
9
8
|
gem 'reek', '~> 3.11', '>= 3.11'
|
10
9
|
gem 'test-unit', '~> 3.0.0', '>= 3.0.0'
|
data/README.md
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
# bnchmrkr
|
2
2
|
|
3
|
-
|
3
|
+
[![build status](https://travis-ci.org/chorankates/bnchmrkr.svg)](https://travis-ci.org/chorankates/bnchmrkr) [![Gem Version](https://badge.fury.io/rb/bnchmrkr.png)](https://rubygems.org/gems/bnchmrkr)
|
4
4
|
|
5
|
-
Bnchmrkr (Benchmarker) is a tool to help benchmark different method implementations
|
5
|
+
Bnchmrkr (Benchmarker) is a tool to help benchmark different method implementations in Ruby
|
6
6
|
|
7
7
|
it is driven by [Benchmark](http://ruby-doc.org/stdlib-2.0.0/libdoc/benchmark/rdoc/Benchmark.html)
|
8
8
|
|
9
|
+
i hate the name too, [but..](https://github.com/chorankates/bnchmrkr/issues/1)
|
10
|
+
|
11
|
+
- [usage](#usage)
|
12
|
+
- [pre-built gem installation (stable)](#pre-built-gem-installation-stable)
|
13
|
+
- [from-source installation (latest)](#from-source-installation-latest)
|
14
|
+
- [examples](#examples)
|
15
|
+
- [code](#code)
|
16
|
+
- [output](#output)
|
17
|
+
- [converting from < 0.1.1](#upgrading)
|
18
|
+
- [methods](#methods)
|
19
|
+
- [Bnchmrkr](#bnchmrkr)
|
20
|
+
- [Bnchmrkr::Mark](#bnchmrkrmark)
|
21
|
+
|
9
22
|
## usage
|
10
23
|
|
11
24
|
### pre-built gem installation (stable)
|
12
25
|
|
13
|
-
[![Gem Version](https://badge.fury.io/rb/bnchmrkr.png)](https://rubygems.org/gems/bnchmrkr)
|
14
|
-
|
15
26
|
```sh
|
16
27
|
gem install bnchmrkr
|
17
28
|
irb
|
@@ -22,8 +33,6 @@ irb(main):001:0> require 'bnchmrkr'
|
|
22
33
|
|
23
34
|
### from-source installation (latest)
|
24
35
|
|
25
|
-
[![build status](https://travis-ci.org/chorankates/bnchmrkr.svg)](https://travis-ci.org/chorankates/bnchmrkr)
|
26
|
-
|
27
36
|
```sh
|
28
37
|
git clone https://github.com/chorankates/bnchmrkr.git
|
29
38
|
cd bnchmrkr
|
@@ -37,6 +46,8 @@ irb(main):001:0> require 'bnchmrkr'
|
|
37
46
|
|
38
47
|
## examples
|
39
48
|
|
49
|
+
### code
|
50
|
+
|
40
51
|
```rb
|
41
52
|
tester = Bnchmrkr.new({
|
42
53
|
:count_to_1k => lambda { 1.upto(1000).each { |i| i } },
|
@@ -51,6 +62,8 @@ tester.benchmark!
|
|
51
62
|
puts tester
|
52
63
|
```
|
53
64
|
|
65
|
+
### output
|
66
|
+
|
54
67
|
```
|
55
68
|
$ ruby examples/ls_vs_stat.rb
|
56
69
|
fastest by type(ls) => 0.005704
|
@@ -76,16 +89,60 @@ overall:
|
|
76
89
|
slowest => stat [0.076243]
|
77
90
|
```
|
78
91
|
|
79
|
-
|
92
|
+
### upgrading
|
93
|
+
|
94
|
+
`Bnchmrkr` follows semantic versioning, which allowed a breaking change from versions `0.1.1` to `0.2.0`
|
95
|
+
|
96
|
+
`0.2.0` significantly improves the API and implementation for `Bnchmrkr`, and while upgrading will require some client side changes, they shouldn't be too onerous:
|
97
|
+
|
98
|
+
for the most part, you should be able to update the Hash lookup key with a method call of the same name,
|
99
|
+
|
100
|
+
#### < 0.1.1
|
101
|
+
```rb
|
102
|
+
assert_equal(:iterative, tester.fastest_overall[:name])
|
103
|
+
assert_equal(:recursive, tester.slowest_overall[:name])
|
104
|
+
```
|
105
|
+
|
106
|
+
#### >= 0.2.0
|
107
|
+
```rb
|
108
|
+
assert_equal(:iterative, tester.fastest_overall.name)
|
109
|
+
assert_equal(:recursive, tester.slowest_overall.name)
|
110
|
+
```
|
111
|
+
|
112
|
+
## methods
|
113
|
+
|
114
|
+
### `Bnchmrkr`
|
115
|
+
```rb
|
116
|
+
attr_reader :executions, :marks, :fastest, :slowest
|
117
|
+
...
|
118
|
+
def initialize(lambdas, executions = 100)
|
119
|
+
def types
|
120
|
+
def benchmark!
|
121
|
+
def inspect
|
122
|
+
def to_s
|
123
|
+
def fastest_by_type(type, mode = :real)
|
124
|
+
def slowest_by_type(type, mode = :real)
|
125
|
+
def fastest_overall
|
126
|
+
def slowest_overall
|
127
|
+
def is_faster?(a, b, mode = :real)
|
128
|
+
def is_slower?(a, b, mode = :real)
|
129
|
+
def faster_by_result(a, b, percent = true, mode = :real)
|
130
|
+
def faster_by_type(a, b, percent = true, mode = :real)
|
131
|
+
def slower_by_type(a, b, percent = true)
|
132
|
+
def slower_by_result(a, b, percent = true)
|
133
|
+
def calculate_overall(mode = :real)
|
134
|
+
```
|
135
|
+
|
136
|
+
### `Bnchmrkr::Mark`
|
137
|
+
```rb
|
138
|
+
attr_reader :computed, :lambda, :name, :mode_precision
|
139
|
+
attr_reader :fastest, :slowest, :mean, :median, :mode, :total
|
140
|
+
...
|
141
|
+
def initialize(name, lambda, mode_precision = 0)
|
142
|
+
def add_measure(measure)
|
143
|
+
def each(&block)
|
144
|
+
def compute
|
145
|
+
def inspect
|
146
|
+
def reset_computations
|
147
|
+
```
|
80
148
|
```
|
81
|
-
benchmark!
|
82
|
-
count
|
83
|
-
faster_by
|
84
|
-
fastest_by_type
|
85
|
-
fastest_overall
|
86
|
-
is_faster?
|
87
|
-
is_slower?
|
88
|
-
results
|
89
|
-
slowest_by_type
|
90
|
-
slowest_overall
|
91
|
-
```
|
data/Rakefile
CHANGED
@@ -61,8 +61,13 @@ end
|
|
61
61
|
|
62
62
|
Reek::Rake::Task.new do |t|
|
63
63
|
t.config_file = File.join(BASEDIR, '.reek')
|
64
|
-
t.source_files = '
|
64
|
+
t.source_files = FileList.new('lib/**/*.rb', 'test/**/*.rb')
|
65
65
|
t.reek_opts = '--no-wiki-links'
|
66
66
|
t.fail_on_error = false
|
67
67
|
t.verbose = true
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'install the gem'
|
71
|
+
task :install do
|
72
|
+
sh 'gem install log4r-sequel'
|
68
73
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/bnchmrkr.rb
CHANGED
@@ -1,100 +1,105 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
+
$LOAD_PATH << sprintf('%s/../lib', File.dirname(__FILE__))
|
4
|
+
require 'bnchmrkr/mark'
|
5
|
+
|
3
6
|
require 'benchmark'
|
4
7
|
|
5
8
|
# Bnchmrkr is a tool to help Benchmark.measure {} and compare different method implementations
|
6
9
|
class Bnchmrkr
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(lambdas, count = 100)
|
11
|
+
UNCOMPUTED = :uncomputed
|
11
12
|
|
12
|
-
|
13
|
-
@results = Hash.new
|
14
|
-
@lambdas = lambdas
|
13
|
+
attr_reader :executions, :marks, :fastest, :slowest
|
15
14
|
|
16
|
-
|
17
|
-
@
|
15
|
+
def initialize(lambdas, executions = 100)
|
16
|
+
@executions = executions
|
17
|
+
@marks = Hash.new
|
18
18
|
|
19
|
-
|
19
|
+
# TODO need to cache these computations and allow reseting similar to how Bnchmrkr::Mark works
|
20
|
+
@fastest = UNCOMPUTED
|
21
|
+
@slowest = UNCOMPUTED
|
20
22
|
|
21
|
-
|
23
|
+
lambdas.each_pair do |name, l|
|
22
24
|
unless name.class.eql?(Symbol) and l.class.eql?(Proc)
|
23
25
|
raise ArgumentError.new(sprintf('expecting[Symbol,Proc], got[%s,%s]', name.class, l.class))
|
24
26
|
end
|
27
|
+
|
28
|
+
@marks[name] = Bnchmrkr::Mark.new(name, l)
|
25
29
|
end
|
26
30
|
|
27
|
-
raise ArgumentError.new(sprintf('expecting[Fixnum], got[%s]',
|
31
|
+
raise ArgumentError.new(sprintf('expecting[Fixnum], got[%s]', executions.class)) unless executions.class.eql?(Fixnum)
|
28
32
|
|
29
33
|
end
|
30
34
|
|
31
|
-
# return list of named
|
35
|
+
# return list of named Bnchmrkr::Marks
|
32
36
|
def types
|
33
|
-
@
|
37
|
+
@marks.keys
|
34
38
|
end
|
35
39
|
|
36
|
-
# 10 lines to actually do the work..
|
40
|
+
# < 10 lines to actually do the work..
|
37
41
|
def benchmark!
|
38
|
-
@
|
39
|
-
1.upto(@
|
40
|
-
|
41
|
-
|
42
|
-
l.call
|
43
|
-
}
|
44
|
-
add_measure(name, measure)
|
45
|
-
rescue => e
|
46
|
-
add_measure(sprintf('%s-failed', name).to_sym, Benchmark.measure {})
|
47
|
-
end
|
42
|
+
@marks.each_pair do |_name, mark|
|
43
|
+
1.upto(@executions).each do |_execution|
|
44
|
+
measure = Benchmark.measure { mark.lambda.call }
|
45
|
+
mark.add_measure(measure)
|
48
46
|
end
|
47
|
+
|
48
|
+
mark.compute # this is a safer place to do it than by computing on each measure, but still should consider putting this behind a flag
|
49
49
|
end
|
50
50
|
|
51
|
+
calculate_overall
|
51
52
|
end
|
52
53
|
|
53
|
-
## :specific => fastest, slowest, mean, median, total per lambda
|
54
|
-
## :overall => fastest, slowest for all
|
55
54
|
def inspect
|
56
|
-
{
|
55
|
+
{
|
56
|
+
:fastest => {
|
57
|
+
:name => @fastest.name,
|
58
|
+
:fastest => @fastest.fastest.to_s.chomp,
|
59
|
+
:by => self.faster_by_result(@fastest.fastest, @slowest.slowest),
|
60
|
+
},
|
61
|
+
:slowest => {
|
62
|
+
:name => @slowest.name,
|
63
|
+
:slowest => @slowest.fastest.to_s.chomp,
|
64
|
+
},
|
65
|
+
:meta => {
|
66
|
+
:marks => @marks.keys,
|
67
|
+
:executions => @executions,
|
68
|
+
},
|
69
|
+
}
|
57
70
|
end
|
58
71
|
|
59
72
|
# overly intricate output formatting of overall and specific results
|
60
73
|
def to_s
|
61
74
|
string = String.new
|
62
75
|
inspection = self.inspect
|
63
|
-
return string unless inspection.nil? or inspection.has_key?(:overall)
|
64
76
|
|
65
|
-
longest_key = inspection
|
77
|
+
longest_key = inspection.keys.each { |i| i.length }.max.length + 5
|
66
78
|
|
67
|
-
inspection
|
79
|
+
inspection.keys.each do |i|
|
68
80
|
string << sprintf('%s:%s', i, "\n")
|
69
|
-
inspection[
|
70
|
-
string << sprintf(" %#{longest_key}s => %s%s", k, inspection[
|
81
|
+
inspection[i].keys.each do |k|
|
82
|
+
string << sprintf(" %#{longest_key}s => %s%s", k, inspection[i][k], "\n")
|
71
83
|
end
|
72
84
|
end
|
73
85
|
|
74
|
-
string << sprintf('overall:%s', "\n")
|
75
|
-
inspection[:overall].each_pair do |type, measure|
|
76
|
-
string << sprintf(" %#{longest_key}s => %s [%s]%s%s",
|
77
|
-
type,
|
78
|
-
measure[:name],
|
79
|
-
measure[:measure],
|
80
|
-
measure.has_key?(:faster_by) ? sprintf(' [faster by %s]', measure[:faster_by]) : '',
|
81
|
-
"\n"
|
82
|
-
)
|
83
|
-
end
|
84
|
-
|
85
86
|
string
|
86
87
|
end
|
87
88
|
|
88
89
|
# +type+ name of a lambda that is known
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
results[type].collect { |r| measures << r.real}
|
90
|
+
# +mode+ method to use on the Bnchmrkr::Mark object ot compare (:real, :cstime, :cutime, :stime, :utime, :total)
|
91
|
+
# find and return the fastest Bnchrmrkr::Mark runtime per lambda of +type+
|
92
|
+
def fastest_by_type(type, mode = :real)
|
93
|
+
return UNCOMPUTED unless @marks.has_key?(type)
|
94
|
+
@marks[type].fastest.__send__(mode)
|
95
|
+
end
|
96
96
|
|
97
|
-
|
97
|
+
# +type+ name of a lambda that is known
|
98
|
+
# +mode+ method to use on the Bnchmrkr::Mark object ot compare (:real, :cstime, :cutime, :stime, :utime, :total)
|
99
|
+
# find and return the slowest Bnchrmrkr::Mark runtime per lambda of +type+
|
100
|
+
def slowest_by_type(type, mode = :real)
|
101
|
+
return UNCOMPUTED unless @marks.has_key?(type)
|
102
|
+
@marks[type].slowest.__send__(mode)
|
98
103
|
end
|
99
104
|
|
100
105
|
# find and return the fastest overall execution (regardless of lambda type)
|
@@ -103,18 +108,6 @@ class Bnchmrkr
|
|
103
108
|
@fastest
|
104
109
|
end
|
105
110
|
|
106
|
-
# +type+ name of a lambda that is known
|
107
|
-
# find and return the slowest execution per lambda of +type+
|
108
|
-
def slowest_by_type(type)
|
109
|
-
results = @results
|
110
|
-
measures = Array.new
|
111
|
-
|
112
|
-
return nil unless results.has_key?(type)
|
113
|
-
results[type].collect { |r| measures << r.real }
|
114
|
-
|
115
|
-
measures.sort.last
|
116
|
-
end
|
117
|
-
|
118
111
|
# find and return the slowest overall execution (regardless of lambda type)
|
119
112
|
def slowest_overall
|
120
113
|
calculate_overall
|
@@ -125,28 +118,31 @@ class Bnchmrkr
|
|
125
118
|
# +b+ Symbol that represents a known lambda
|
126
119
|
# +mode+ :fastest, :slowest, :mean, :median, :total
|
127
120
|
# return boolean if a is faster than b, false if invalid
|
128
|
-
def is_faster?(a, b, mode = :
|
129
|
-
|
130
|
-
|
131
|
-
|
121
|
+
def is_faster?(a, b, mode = :real)
|
122
|
+
return false unless @marks.has_key?(a) and @marks.has_key?(b)
|
123
|
+
# TODO not sure that we're doing the right thing here.. the fastest fast run should always be faster than the slowest slow run.. but should we be comparing fastest fast with slowest fast?
|
124
|
+
@marks[a].fastest.__send__(mode) < @marks[b].fastest.__send__(mode)
|
132
125
|
end
|
133
126
|
|
134
|
-
# +a+
|
135
|
-
# +b+
|
127
|
+
# +a+ Bnchmrkr::Mark
|
128
|
+
# +b+ Bnchmrkr::Mark
|
136
129
|
# +mode+ :fastest, :slowest, :mean, :median, :total
|
137
130
|
# return boolean if a is faster than b, false if invalid
|
138
|
-
def is_slower?(a, b, mode = :
|
131
|
+
def is_slower?(a, b, mode = :real)
|
139
132
|
! is_faster?(a, b, mode)
|
140
133
|
end
|
141
134
|
|
142
|
-
# +a+
|
143
|
-
# +b+
|
135
|
+
# +a+ Bnchmrkr::Mark
|
136
|
+
# +b+ Bnchmrkr::Mark
|
144
137
|
# +percent+ Boolean representing percent (String) or Float difference
|
145
138
|
# return Float representing difference in measures, or false, if b is slower than a
|
146
|
-
def faster_by_result(a, b, percent = true)
|
147
|
-
|
139
|
+
def faster_by_result(a, b, percent = true, mode = :real)
|
140
|
+
measure_a = a.__send__(mode)
|
141
|
+
measure_b = b.__send__(mode)
|
142
|
+
|
143
|
+
return false if measure_b < measure_a
|
148
144
|
|
149
|
-
faster = (
|
145
|
+
faster = (measure_b - measure_a) / measure_a
|
150
146
|
percent ? sprintf('%4f%', faster * 100) : faster
|
151
147
|
end
|
152
148
|
|
@@ -154,9 +150,9 @@ class Bnchmrkr
|
|
154
150
|
# +b+ Symbol representing name of known lambda type
|
155
151
|
# +percent+ Boolean representing percent (String) or Float difference
|
156
152
|
# return Float representing difference in measures, or false, if b is slower than a
|
157
|
-
def faster_by_type(a, b, percent = true)
|
158
|
-
fastest_a = fastest_by_type(a)
|
159
|
-
fastest_b = fastest_by_type(b)
|
153
|
+
def faster_by_type(a, b, percent = true, mode = :real)
|
154
|
+
fastest_a = fastest_by_type(a).__send__(mode)
|
155
|
+
fastest_b = fastest_by_type(b).__send__(mode)
|
160
156
|
|
161
157
|
return false if fastest_b < fastest_a
|
162
158
|
|
@@ -174,63 +170,27 @@ class Bnchmrkr
|
|
174
170
|
|
175
171
|
private
|
176
172
|
|
177
|
-
|
178
|
-
|
179
|
-
self.results[name] << measure
|
180
|
-
end
|
181
|
-
|
182
|
-
# +mode_precision+ Fixnum indicating number of digits to consider during mode calculation, default to 0, which will use all signal
|
183
|
-
# from existing results, generate some statistics per lambda type
|
184
|
-
def calculate_per_lambda(mode_precision = 0)
|
185
|
-
hash = Hash.new
|
186
|
-
|
187
|
-
# TODO come up with way to not recompute unless contents have changed -- https://github.com/chorankates/bnchmrkr/issues/3
|
188
|
-
|
189
|
-
@results.each_pair do |name, measures|
|
190
|
-
hash[name] = Hash.new
|
191
|
-
frequency_hash = Hash.new(0)
|
192
|
-
mode_candidate = { :frequency => 1, :operand => nil }
|
193
|
-
total = 0
|
194
|
-
|
195
|
-
sorted = measures.sort { |a,b| a.real <=> b.real }
|
196
|
-
measures.each do |measure|
|
197
|
-
operand = mode_precision.equal?(0) ? measure.real : measure.real.round(mode_precision)
|
198
|
-
frequency_hash[operand] += 1
|
173
|
+
# update fastest/slowest, return in a named Hash
|
174
|
+
def calculate_overall(mode = :real)
|
199
175
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
mode_candidate[:operand] = operand
|
204
|
-
end
|
176
|
+
@marks.each_pair do |_name, mark|
|
177
|
+
if @fastest.eql?(:uncomputed) or mark.fastest.__send__(mode) < @fastest.fastest.__send__(mode)
|
178
|
+
@fastest = mark
|
205
179
|
end
|
206
180
|
|
207
|
-
|
208
|
-
|
209
|
-
hash[name][:slowest] = sorted.last.real
|
210
|
-
hash[name][:mean] = sprintf('%5f', total / sorted.size).to_f
|
211
|
-
hash[name][:median] = sorted[(sorted.size / 2)].real
|
212
|
-
# TODO need to handle the rare case that we have multiple modes -- https://github.com/chorankates/bnchmrkr/issues/4
|
213
|
-
hash[name][:mode] = mode_candidate[:operand] # collect key name with highest value
|
214
|
-
hash[name][:total] = sprintf('%5f', total).to_f
|
215
|
-
end
|
216
|
-
|
217
|
-
hash
|
218
|
-
end
|
219
|
-
|
220
|
-
# update fastest/slowest, return in a named Hash
|
221
|
-
def calculate_overall
|
222
|
-
calculate_per_lambda.each_pair do |name,results|
|
223
|
-
if results[:fastest] < @fastest[:measure]
|
224
|
-
@fastest = { :name => name, :measure => results[:fastest] }
|
181
|
+
if @slowest.eql?(:uncomputed) or mark.slowest.__send__(mode) > @slowest.slowest.__send__(mode)
|
182
|
+
@slowest = mark
|
225
183
|
end
|
226
184
|
|
227
|
-
if results[:slowest] > @slowest[:measure]
|
228
|
-
@slowest = { :name => name, :measure => results[:slowest] }
|
229
|
-
end
|
230
185
|
end
|
231
186
|
|
232
|
-
|
233
|
-
|
187
|
+
{
|
188
|
+
:fastest => @fastest.fastest.__send__(mode),
|
189
|
+
:fastest_name => @fastest.name,
|
190
|
+
:slowest => @slowest.slowest.__send__(mode),
|
191
|
+
:slowest_name => @slowest.name,
|
192
|
+
:faster_by => self.faster_by_result(@fastest.fastest, @slowest.slowest)
|
193
|
+
}
|
234
194
|
end
|
235
195
|
|
236
|
-
end
|
196
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# oh ruby
|
2
|
+
class Bnchmrkr; end
|
3
|
+
|
4
|
+
# Bnchmrkr::Mark represents individual lambda runs within a Bnchmrkr run
|
5
|
+
class Bnchmrkr::Mark
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :computed, :lambda, :name, :mode_precision
|
9
|
+
attr_reader :fastest, :slowest, :mean, :median, :mode, :total
|
10
|
+
|
11
|
+
def initialize(name, lambda, mode_precision = 0)
|
12
|
+
@name = name
|
13
|
+
@lambda = lambda # TODO this name is going to cause problems
|
14
|
+
@measures = Array.new
|
15
|
+
|
16
|
+
@mode_precision = mode_precision
|
17
|
+
|
18
|
+
reset_computations # initialize to known values
|
19
|
+
end
|
20
|
+
|
21
|
+
# +measure+ Benchmark.measure{} result
|
22
|
+
def add_measure(measure)
|
23
|
+
@measures << measure
|
24
|
+
reset_computations if @computed # as soon as a measure is added, previous computations are invalid
|
25
|
+
end
|
26
|
+
|
27
|
+
# allows Array-like behavior on the Array of measurements contained in a Bnchmrkr::Mark
|
28
|
+
def each(&block)
|
29
|
+
@measures.each(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def compute
|
33
|
+
frequency_hash = Hash.new(0)
|
34
|
+
total = 0
|
35
|
+
|
36
|
+
sorted = @measures.sort { |a,b| a.real <=> b.real }
|
37
|
+
|
38
|
+
@measures.each do |measure|
|
39
|
+
operand = @mode_precision.equal?(0) ? measure.real : measure.real.round(@mode_precision)
|
40
|
+
frequency_hash[operand] += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
max_frequency = frequency_hash.values.max
|
44
|
+
mode_candidate = frequency_hash.select{ |_operand, frequency| frequency.equal?(max_frequency) }.keys
|
45
|
+
|
46
|
+
if max_frequency.equal?(1)
|
47
|
+
mode = nil
|
48
|
+
else
|
49
|
+
mode = mode_candidate.size > 1 ? mode_candidate : mode_candidate.first
|
50
|
+
end
|
51
|
+
|
52
|
+
sorted.collect { |r| total += r.real }
|
53
|
+
@fastest = sorted.first
|
54
|
+
@slowest = sorted.last
|
55
|
+
@mean = sprintf('%5f', total / sorted.size).to_f
|
56
|
+
@median = sorted[(sorted.size / 2)]
|
57
|
+
@mode = mode
|
58
|
+
@total = sprintf('%5f', total).to_f
|
59
|
+
|
60
|
+
@computed = true
|
61
|
+
end
|
62
|
+
|
63
|
+
# a semi-fancy and somewhat fragile inspect method
|
64
|
+
## try to compute if we haven't been, ensuring we always return a hash
|
65
|
+
def inspect
|
66
|
+
begin
|
67
|
+
self.compute unless @computed
|
68
|
+
{
|
69
|
+
:name => @name,
|
70
|
+
:fastest => @fastest.real,
|
71
|
+
:slowest => @slowest.real,
|
72
|
+
:mean => @mean,
|
73
|
+
:median => @median,
|
74
|
+
:mode => @mode,
|
75
|
+
:total => @total,
|
76
|
+
}
|
77
|
+
rescue => e
|
78
|
+
{ :name => @name, :computed => @computed }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# reset internal computed values, used when a new measure is added
|
85
|
+
def reset_computations
|
86
|
+
@computed = false
|
87
|
+
@fastest = :uncomputed
|
88
|
+
@slowest = :uncomputed
|
89
|
+
@mean = :uncomputed
|
90
|
+
@median = :uncomputed
|
91
|
+
@mode = :uncomputed
|
92
|
+
@total = :uncomputed
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -12,8 +12,10 @@ class TestExamples < Test::Unit::TestCase
|
|
12
12
|
@files.each do |file|
|
13
13
|
raw = `ruby #{file}`
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
generic_failure_message = sprintf('[%s] returned [%s]: [%s]', File.basename(file), $?, raw)
|
16
|
+
|
17
|
+
assert_true($?.success?, generic_failure_message)
|
18
|
+
assert_not_nil(raw, generic_failure_message)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
data/test/unit/test_contrived.rb
CHANGED
@@ -17,74 +17,85 @@ class TestContrived < Test::Unit::TestCase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_fastest
|
20
|
-
assert_equal(:count_to_1k, @tester.fastest_overall
|
21
|
-
assert_true(@tester.fastest_overall
|
20
|
+
assert_equal(:count_to_1k, @tester.fastest_overall.name)
|
21
|
+
assert_true(@tester.fastest_overall.fastest.real < @tester.slowest_overall.slowest.real)
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_slowest
|
25
25
|
slowest_overall = @tester.slowest_overall
|
26
26
|
fastest_overall = @tester.fastest_overall
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fastest_overall, slowest_overall)
|
29
|
+
|
30
|
+
assert_equal(:count_to_100k, slowest_overall.name, generic_failure_message)
|
31
|
+
assert_true(slowest_overall.slowest.real > fastest_overall.fastest.real, generic_failure_message)
|
30
32
|
end
|
31
33
|
|
32
34
|
def test_speed_by_type
|
33
35
|
@tester.types.each do |type|
|
34
|
-
slowest = @tester.slowest_by_type(type)
|
35
|
-
fastest = @tester.fastest_by_type(type)
|
36
|
+
slowest = @tester.slowest_by_type(type).real
|
37
|
+
fastest = @tester.fastest_by_type(type).real
|
38
|
+
|
39
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fastest, slowest)
|
36
40
|
|
37
|
-
assert_true(slowest > fastest)
|
41
|
+
assert_true(slowest > fastest, generic_failure_message)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
45
|
def test_is_faster?
|
42
|
-
fast = @tester.fastest_overall
|
43
|
-
slow = @tester.slowest_overall
|
46
|
+
fast = @tester.fastest_overall.name
|
47
|
+
slow = @tester.slowest_overall.name
|
48
|
+
|
49
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fast, slow)
|
44
50
|
|
45
51
|
forward = @tester.is_faster?(fast, slow)
|
46
52
|
reverse = @tester.is_faster?(slow, fast)
|
47
53
|
equal = @tester.is_faster?(fast, fast)
|
48
54
|
|
49
|
-
assert_not_equal(forward, reverse)
|
50
|
-
assert_true(forward)
|
51
|
-
assert_false(reverse)
|
52
|
-
assert_false(equal)
|
55
|
+
assert_not_equal(forward, reverse, generic_failure_message)
|
56
|
+
assert_true(forward, generic_failure_message)
|
57
|
+
assert_false(reverse, generic_failure_message)
|
58
|
+
assert_false(equal, generic_failure_message)
|
53
59
|
end
|
54
60
|
|
55
61
|
def test_is_slower?
|
56
|
-
fast = @tester.fastest_overall
|
57
|
-
slow = @tester.slowest_overall
|
62
|
+
fast = @tester.fastest_overall.name
|
63
|
+
slow = @tester.slowest_overall.name
|
64
|
+
|
65
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fast, slow)
|
58
66
|
|
59
67
|
forward = @tester.is_slower?(slow, fast)
|
60
68
|
reverse = @tester.is_slower?(fast, slow)
|
61
69
|
equal = @tester.is_slower?(slow, slow)
|
62
70
|
|
63
|
-
assert_not_equal(forward, reverse)
|
64
|
-
assert_true(forward)
|
65
|
-
assert_false(reverse)
|
66
|
-
assert_true(equal
|
71
|
+
assert_not_equal(forward, reverse, generic_failure_message)
|
72
|
+
assert_true(forward, generic_failure_message)
|
73
|
+
assert_false(reverse, generic_failure_message)
|
74
|
+
assert_true(equal, sprintf('fast[%s] slow[%s]', fast, slow))
|
67
75
|
end
|
68
76
|
|
69
77
|
def test_faster_by_result
|
70
|
-
fast = @tester.fastest_overall
|
71
|
-
slow = @tester.slowest_overall
|
78
|
+
fast = @tester.fastest_overall.fastest
|
79
|
+
slow = @tester.slowest_overall.slowest
|
80
|
+
|
81
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fast, slow)
|
72
82
|
|
73
|
-
assert_not_nil(@tester.faster_by_result(fast, slow, true))
|
74
|
-
assert_match(/\d+\.\d+%/, @tester.faster_by_result(fast, slow, true))
|
75
|
-
assert_true(@tester.faster_by_result(fast, slow, false).is_a?(Float))
|
76
|
-
assert_false(@tester.faster_by_result(slow, fast))
|
83
|
+
assert_not_nil(@tester.faster_by_result(fast, slow, true), generic_failure_message)
|
84
|
+
assert_match(/\d+\.\d+%/, @tester.faster_by_result(fast, slow, true), generic_failure_message)
|
85
|
+
assert_true(@tester.faster_by_result(fast, slow, false).is_a?(Float), generic_failure_message)
|
86
|
+
assert_false(@tester.faster_by_result(slow, fast), generic_failure_message)
|
77
87
|
end
|
78
88
|
|
79
89
|
def test_faster_by_type
|
80
|
-
fastest = @tester.fastest_overall
|
81
|
-
slowest = @tester.slowest_overall
|
90
|
+
fastest = @tester.fastest_overall.name
|
91
|
+
slowest = @tester.slowest_overall.name
|
82
92
|
|
83
|
-
|
84
|
-
assert_match(/\d+\.\d+%/, @tester.faster_by_type(fastest, slowest, true))
|
85
|
-
assert_true(@tester.faster_by_type(fastest, slowest, false).is_a?(Float))
|
86
|
-
assert_false(@tester.faster_by_type(slowest, fastest))
|
93
|
+
generic_failure_message = sprintf('fast[%s] slow[%s]', fastest, slowest)
|
87
94
|
|
95
|
+
assert_not_nil(@tester.faster_by_type(fastest, slowest, true), generic_failure_message)
|
96
|
+
assert_match(/\d+\.\d+%/, @tester.faster_by_type(fastest, slowest, true), generic_failure_message)
|
97
|
+
assert_true(@tester.faster_by_type(fastest, slowest, false).is_a?(Float), generic_failure_message)
|
98
|
+
assert_false(@tester.faster_by_type(slowest, fastest), generic_failure_message)
|
88
99
|
end
|
89
100
|
|
90
101
|
end
|
@@ -6,17 +6,21 @@ class TestExceptions < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
def setup; end
|
8
8
|
|
9
|
-
def test_lambdas_will_raise
|
9
|
+
def test_lambdas_will_raise # but we will catch them
|
10
|
+
symbol = :divbyzero
|
11
|
+
|
10
12
|
tester = Bnchmrkr.new({
|
11
|
-
|
13
|
+
symbol => lambda {
|
12
14
|
10 / 0
|
13
15
|
},
|
14
16
|
})
|
15
17
|
|
16
|
-
|
18
|
+
e = assert_raise do
|
17
19
|
tester.benchmark!
|
18
|
-
assert_true(tester.results.has_key?('divbyzero-failed'.to_sym))
|
19
20
|
end
|
21
|
+
|
22
|
+
assert_true(tester.marks.has_key?(symbol))
|
23
|
+
assert_equal(ZeroDivisionError, e.class)
|
20
24
|
end
|
21
25
|
|
22
26
|
def test_lambdas_wont_raise_anyway
|
@@ -26,7 +30,7 @@ class TestExceptions < Test::Unit::TestCase
|
|
26
30
|
|
27
31
|
assert_nothing_raised do
|
28
32
|
tester.benchmark!
|
29
|
-
assert_true(tester.
|
33
|
+
assert_true(tester.marks.has_key?(:foo))
|
30
34
|
end
|
31
35
|
|
32
36
|
end
|
@@ -19,6 +19,19 @@ class TestInitialize < Test::Unit::TestCase
|
|
19
19
|
|
20
20
|
end
|
21
21
|
|
22
|
+
# TODO this is really testing Bnchmrkr::Mark objects
|
23
|
+
def test_initial_values
|
24
|
+
|
25
|
+
bnchmrkr = Bnchmrkr.new({:foo => lambda {}})
|
26
|
+
|
27
|
+
bnchmrkr.marks.each_pair do |name, m|
|
28
|
+
[ :fastest, :slowest, :mean, :median, :mode, :total ].each do |a|
|
29
|
+
assert_equal(:uncomputed, m.__send__(a), sprintf('failed[%s] for[%s]', a, name))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
22
35
|
def test_invalid
|
23
36
|
|
24
37
|
# not a proc
|
@@ -49,8 +62,6 @@ class TestInitialize < Test::Unit::TestCase
|
|
49
62
|
Bnchmrkr.new({:foo => :bar}, 1.1)
|
50
63
|
end
|
51
64
|
|
52
|
-
|
53
|
-
|
54
65
|
end
|
55
66
|
|
56
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bnchmrkr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Conor Horan-Kates
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
- - ~>
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 10.5.0
|
23
|
-
type: :
|
23
|
+
type: :development
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- Rakefile
|
107
107
|
- VERSION
|
108
108
|
- lib/bnchmrkr.rb
|
109
|
+
- lib/bnchmrkr/mark.rb
|
109
110
|
- test/examples/test_examples.rb
|
110
111
|
- test/unit/test_contrived.rb
|
111
112
|
- test/unit/test_exceptions.rb
|
@@ -130,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
131
|
version: '0'
|
131
132
|
requirements: []
|
132
133
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.
|
134
|
+
rubygems_version: 2.4.8
|
134
135
|
signing_key:
|
135
136
|
specification_version: 4
|
136
137
|
summary: compare execution time
|