benchmark-bigo 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +119 -15
- data/lib/benchmark/bigo.rb +4 -8
- data/lib/benchmark/bigo/chart.rb +148 -0
- data/lib/benchmark/bigo/job.rb +102 -75
- data/lib/benchmark/bigo/report.rb +9 -93
- data/lib/benchmark/bigo/templates/chart.erb +1 -0
- data/lib/benchmark/bigo/termplot.rb +70 -0
- data/lib/benchmark/bigo/version.rb +2 -2
- data/test/benchmark/test_bigo.rb +174 -82
- data/test/benchmark/test_chart.rb +89 -0
- data/test/benchmark/test_termplot.rb +52 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2001993a5384ac4c261dfcfab54718bd699ebe5
|
4
|
+
data.tar.gz: 9126a20c3623302fee2c997c2264d601164adc21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4a97d657456950c926c63a56cf12dd9b2f60b7b793b1e4b4d305b42cd2e03f0dd82d59674b93ccfaa7a06024922a106b29d1e5eadd0dcd85ed845bffcf2e6ca
|
7
|
+
data.tar.gz: f414c0c8f1a215ccca45103e543e60486700a1133544f5ed6799d151610c30d0fe3db312d30ec009e2c6c62db22b9108edf04fdb2cd31018a1a1607e3ba7fc7d
|
data/README.md
CHANGED
@@ -23,10 +23,7 @@ $ gem install benchmark-bigo
|
|
23
23
|
```ruby
|
24
24
|
require 'benchmark/bigo'
|
25
25
|
|
26
|
-
|
27
|
-
# increments is the total number of data points to collect
|
28
|
-
x.increments = 6
|
29
|
-
|
26
|
+
Benchmark.bigo do |x|
|
30
27
|
# generator should construct a test object of the given size
|
31
28
|
# example of an Array generator
|
32
29
|
x.generator {|size| (0...size).to_a.shuffle }
|
@@ -34,20 +31,23 @@ report = Benchmark.bigo do |x|
|
|
34
31
|
# or you can use the built in array generator
|
35
32
|
# x.generate :array
|
36
33
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
# steps is the total number of data points to collect
|
35
|
+
# default is 10
|
36
|
+
x.steps = 6
|
37
|
+
|
38
|
+
# step_size is the size between steps
|
39
|
+
# default is 100
|
40
|
+
x.step_size = 200
|
41
|
+
|
42
|
+
# indicates the starting size of the object to test
|
43
|
+
# default is 100
|
44
|
+
x.min_size = 1000
|
42
45
|
|
43
46
|
# report takes a label and a block.
|
44
47
|
# block is passed in the generated object and the size of that object
|
45
|
-
x.report("#at")
|
46
|
-
x.report("#index")
|
47
|
-
x.report("#
|
48
|
-
|
49
|
-
# save results in JSON format
|
50
|
-
x.data! 'chart_array_simple.json'
|
48
|
+
x.report("#at") {|array, size| array.at rand(size) }
|
49
|
+
x.report("#index") {|array, size| array.index rand(size) }
|
50
|
+
x.report("#index-miss") {|array, size| array.index (size + rand(size)) }
|
51
51
|
|
52
52
|
# generate HTML chart using ChartKick
|
53
53
|
x.chart! 'chart_array_simple.html'
|
@@ -55,9 +55,113 @@ report = Benchmark.bigo do |x|
|
|
55
55
|
# for each report, create a comparison chart showing the report
|
56
56
|
# and scaled series for O(log n), O(n), O(n log n), and O(n squared)
|
57
57
|
x.compare!
|
58
|
+
|
59
|
+
# generate an ASCII chart using gnuplot
|
60
|
+
# works best with only one or two reports
|
61
|
+
# otherwise the lines often overlap each other
|
62
|
+
x.termplot!
|
63
|
+
|
64
|
+
# generate JSON output
|
65
|
+
x.json! 'chart_array_simple.json'
|
66
|
+
|
67
|
+
# generate CSV output
|
68
|
+
x.csv! 'chart_array_simple.csv'
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
## Generators
|
73
|
+
|
74
|
+
In order to test a block of code on an input of varying size, the benchmark tool must know how to generate this varying input. **Generators** are used to create this input to be tested. In many cases the generator creates an object, but this is not the only way generators can be used.
|
75
|
+
|
76
|
+
There are a few built-in generators to make it easy to test common objects.
|
77
|
+
|
78
|
+
### Array
|
79
|
+
|
80
|
+
The Array generator is used by calling
|
81
|
+
|
82
|
+
```
|
83
|
+
Benchmark.bigo do |x|
|
84
|
+
x.generate :array
|
85
|
+
...
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
This generator knows how to create Arrays of varying sizes. The generated Array contains the set of numbers between 0 and (size-1), randomly shuffled. For example, a generated Array of size 5 might look like:
|
90
|
+
|
91
|
+
```
|
92
|
+
[3,2,4,1,0]
|
93
|
+
```
|
94
|
+
|
95
|
+
### String
|
96
|
+
|
97
|
+
The String generator is used by calling
|
98
|
+
|
99
|
+
```
|
100
|
+
Benchmark.bigo do |x|
|
101
|
+
x.generate :string
|
102
|
+
...
|
58
103
|
end
|
59
104
|
```
|
60
105
|
|
106
|
+
This generator uses `SecureRandom.hex` to create random hexadecimal string of varying sizes. In this case the size indicates the length in *bytes* of the string to be generated. This means that the resulting string will be 2 * size. For example, a generated String of size 10 might look like:
|
107
|
+
|
108
|
+
```
|
109
|
+
"70fd739c8640b8c9212b"
|
110
|
+
```
|
111
|
+
|
112
|
+
### Size
|
113
|
+
|
114
|
+
This generator is useful when there isn't a direct object to be tested, and instead the benchmark is testing the impact of the size within the report blocks themselves. In this case, the two variables passed to the report block will both be the size value. The example shows how you might test the performance difference for iteration operations.
|
115
|
+
|
116
|
+
Example:
|
117
|
+
|
118
|
+
```
|
119
|
+
Benchmark.bigo do |x|
|
120
|
+
x.generate :size
|
121
|
+
|
122
|
+
x.report("#times") {|size,_|
|
123
|
+
|
124
|
+
size.times do |i|
|
125
|
+
# do things here
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
x.report("for") {|size,_|
|
130
|
+
|
131
|
+
for i in size
|
132
|
+
# do things here
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
### Custom
|
140
|
+
|
141
|
+
There are many cases where custom generators are needed. This is the case if the code you wish to benchmark
|
142
|
+
runs against a specific type of input. A custom generator is created by specifying a block that accepts a single size parameter and returns an object of that size.
|
143
|
+
|
144
|
+
The following generator creates a Hash where each of the keys is the integer of the size and the value is a random string 10 characters long.
|
145
|
+
|
146
|
+
```
|
147
|
+
Benchmark.bigo do |x|
|
148
|
+
x.generator do |size|
|
149
|
+
h = {}
|
150
|
+
(0...size).each {|i| h[i] = SecureRandom.hex(10)}
|
151
|
+
h
|
152
|
+
end
|
153
|
+
...
|
154
|
+
end
|
155
|
+
|
156
|
+
# for size 5
|
157
|
+
# => { 0 => "1fb3e5bb88815d824fff",
|
158
|
+
# 1 => "63006b6e810502f5bedd",
|
159
|
+
# 2 => "7bef4e15a2b4f9e44fa9",
|
160
|
+
# 3 => "f78319925f8c0790b409",
|
161
|
+
# 4 => "2bfc6a0a91c4949dc6ae"
|
162
|
+
# }
|
163
|
+
```
|
164
|
+
|
61
165
|
## Contributing
|
62
166
|
|
63
167
|
After checking out the source, run the tests:
|
data/lib/benchmark/bigo.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'erb'
|
3
3
|
require 'chartkick'
|
4
|
+
require 'csv'
|
4
5
|
require 'benchmark/ips'
|
5
6
|
require 'benchmark/bigo/report'
|
7
|
+
require 'benchmark/bigo/chart'
|
8
|
+
require 'benchmark/bigo/termplot'
|
6
9
|
require 'benchmark/bigo/job'
|
7
10
|
|
8
11
|
module Benchmark
|
@@ -32,14 +35,7 @@ module Benchmark
|
|
32
35
|
$stdout.puts "-------------------------------------------------" unless quiet
|
33
36
|
|
34
37
|
job.run
|
35
|
-
|
36
|
-
if job.data?
|
37
|
-
job.generate_data
|
38
|
-
end
|
39
|
-
|
40
|
-
if job.chart?
|
41
|
-
job.generate_chart
|
42
|
-
end
|
38
|
+
job.generate_output
|
43
39
|
|
44
40
|
$stdout.sync = sync
|
45
41
|
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Benchmark
|
2
|
+
|
3
|
+
module BigO
|
4
|
+
class Chart
|
5
|
+
|
6
|
+
TYPES = [:const, :logn, :n, :nlogn, :n_sq]
|
7
|
+
attr_accessor :sample_size
|
8
|
+
|
9
|
+
def initialize report_data, sizes
|
10
|
+
@data = report_data.freeze
|
11
|
+
@sizes = sizes.freeze
|
12
|
+
|
13
|
+
@sample_size = @sizes.first
|
14
|
+
|
15
|
+
# can't take log of 1,
|
16
|
+
# so it can't be used as the sample
|
17
|
+
if @sample_size == 1
|
18
|
+
@sample_size = @sizes[1]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate config={}
|
23
|
+
|
24
|
+
charts = [ { name: 'Growth Chart',
|
25
|
+
data: @data,
|
26
|
+
opts: opts_for(@data) } ]
|
27
|
+
|
28
|
+
if config[:compare]
|
29
|
+
for entry_data in @data
|
30
|
+
charts << { name: entry_data[:name],
|
31
|
+
data: comparison_for(entry_data),
|
32
|
+
opts: opts_for([entry_data]) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
charts
|
37
|
+
end
|
38
|
+
|
39
|
+
def opts_for data
|
40
|
+
data = [data] unless Array === data
|
41
|
+
|
42
|
+
min = data.collect{|d| d[:data].values.min }.min
|
43
|
+
max = data.collect{|d| d[:data].values.max }.max
|
44
|
+
|
45
|
+
orange = "#f0662d"
|
46
|
+
purple = "#8062a6"
|
47
|
+
light_green = "#7bc545"
|
48
|
+
med_blue = "#0883b2"
|
49
|
+
yellow = "#ffaa00"
|
50
|
+
teal = "#00c7c3"
|
51
|
+
|
52
|
+
{
|
53
|
+
discrete: true,
|
54
|
+
width: "800px",
|
55
|
+
height: "500px",
|
56
|
+
min: (min * 0.8).floor,
|
57
|
+
max: (max * 1.2).ceil,
|
58
|
+
library: {
|
59
|
+
colors: [orange, purple, light_green, med_blue, yellow, teal],
|
60
|
+
xAxis: {type: 'linear', title: {text: "Size"}},
|
61
|
+
yAxis: {type: 'linear', title: {text: "Microseconds per Iteration"}}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def comparison_for data
|
67
|
+
sample = data[:data][@sample_size]
|
68
|
+
|
69
|
+
comparison = [data]
|
70
|
+
|
71
|
+
TYPES.each do |type|
|
72
|
+
comparison << generate_data_for(type, sample)
|
73
|
+
end
|
74
|
+
|
75
|
+
comparison
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_data_for type, sample
|
79
|
+
|
80
|
+
# for the given sizes, create a hash from an array
|
81
|
+
# the keys of the hash are the sizes
|
82
|
+
# the values are the generated data for this type of comparison
|
83
|
+
data = Hash[ @sizes.map {|n| [n, data_generator(type, n, sample) ] } ]
|
84
|
+
|
85
|
+
{ name: title_for(type), data: data }
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
def title_for type
|
90
|
+
|
91
|
+
case type
|
92
|
+
when :const
|
93
|
+
'const'
|
94
|
+
when :logn
|
95
|
+
'log n'
|
96
|
+
when :n
|
97
|
+
'n'
|
98
|
+
when :nlogn
|
99
|
+
'n log n'
|
100
|
+
when :n_sq
|
101
|
+
'n squared'
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
def data_generator type, n, sample
|
107
|
+
factor = factor_for(type, sample)
|
108
|
+
|
109
|
+
case type
|
110
|
+
when :const
|
111
|
+
factor
|
112
|
+
when :logn
|
113
|
+
Math.log10(n) * factor
|
114
|
+
|
115
|
+
when :n
|
116
|
+
n * factor
|
117
|
+
|
118
|
+
when :nlogn
|
119
|
+
n * Math.log10(n) * factor
|
120
|
+
|
121
|
+
when :n_sq
|
122
|
+
n * n * factor
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# calculate the scaling factor for the given type and sample using sample_size
|
128
|
+
def factor_for type, sample
|
129
|
+
case type
|
130
|
+
when :const
|
131
|
+
sample.to_f
|
132
|
+
when :logn
|
133
|
+
sample.to_f/Math.log10(@sample_size)
|
134
|
+
|
135
|
+
when :n
|
136
|
+
sample.to_f/@sample_size
|
137
|
+
|
138
|
+
when :nlogn
|
139
|
+
sample.to_f/(@sample_size * Math.log10(@sample_size))
|
140
|
+
|
141
|
+
when :n_sq
|
142
|
+
sample.to_f/(@sample_size * @sample_size)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/benchmark/bigo/job.rb
CHANGED
@@ -35,16 +35,9 @@ module Benchmark
|
|
35
35
|
|
36
36
|
include Chartkick::Helper
|
37
37
|
|
38
|
-
|
39
|
-
attr_accessor :increments
|
38
|
+
require 'securerandom'
|
40
39
|
|
41
|
-
|
42
|
-
attr_accessor :logscale
|
43
|
-
|
44
|
-
# whether to generate a chart of the results
|
45
|
-
# if nil, do not generate chart
|
46
|
-
# else string is name of file to write chart out to
|
47
|
-
attr_reader :chart
|
40
|
+
attr_accessor :min_size, :steps, :step_size
|
48
41
|
|
49
42
|
def initialize opts={}
|
50
43
|
super
|
@@ -53,39 +46,60 @@ module Benchmark
|
|
53
46
|
@generator = nil
|
54
47
|
|
55
48
|
# defaults
|
56
|
-
|
57
|
-
@
|
58
|
-
@
|
59
|
-
|
60
|
-
|
49
|
+
@min_size = 100
|
50
|
+
@step_size = 100
|
51
|
+
@steps = 10
|
52
|
+
|
53
|
+
# whether to generate a chart of the results
|
54
|
+
# if nil, do not generate chart
|
55
|
+
# else string is name of file to write chart out to
|
56
|
+
@chart_file = nil
|
57
|
+
|
58
|
+
# whether to generate json output of the results
|
59
|
+
# if nil, do not generate data
|
60
|
+
# else string is name of file to write data out to
|
61
|
+
@json_file = nil
|
62
|
+
|
63
|
+
# whether to generate csv output of the results
|
64
|
+
# if nil, do not generate data
|
65
|
+
# else string is name of file to write data out to
|
66
|
+
@csv_file = nil
|
67
|
+
|
68
|
+
# whether to generate a plot in the terminal
|
69
|
+
# using gnuplot
|
70
|
+
@term_plot = false
|
71
|
+
end
|
72
|
+
|
73
|
+
def max_size
|
74
|
+
@min_size + (@step_size * (@steps-1))
|
75
|
+
# should also equal step(@steps-1)
|
61
76
|
end
|
62
77
|
|
63
78
|
def config opts
|
64
79
|
super
|
65
|
-
@
|
66
|
-
@
|
67
|
-
@
|
80
|
+
@min_size = opts[:min_size] if opts[:min_size]
|
81
|
+
@steps = opts[:steps] if opts[:steps]
|
82
|
+
@step_size = opts[:step_size] if opts[:step_size]
|
68
83
|
end
|
69
84
|
|
70
|
-
def chart
|
71
|
-
@
|
85
|
+
def chart! filename='chart.html'
|
86
|
+
@chart_file = filename
|
72
87
|
end
|
73
88
|
|
74
|
-
def
|
75
|
-
@
|
89
|
+
def compare?
|
90
|
+
@compare
|
76
91
|
end
|
77
92
|
|
78
|
-
def
|
79
|
-
@
|
93
|
+
def termplot!
|
94
|
+
@term_plot = true
|
80
95
|
end
|
81
96
|
|
82
|
-
def
|
83
|
-
@
|
97
|
+
def json! filename='data.json'
|
98
|
+
@json_file = filename
|
84
99
|
end
|
85
100
|
|
86
|
-
def
|
87
|
-
@
|
88
|
-
@full_report.logscale! if @logscale
|
101
|
+
def csv! filename='data.csv'
|
102
|
+
@csv_file = filename
|
89
103
|
end
|
90
104
|
|
91
105
|
def generator &blk
|
@@ -99,43 +113,43 @@ module Benchmark
|
|
99
113
|
# represented by the symbol passed to the method
|
100
114
|
def generate sym
|
101
115
|
|
102
|
-
|
116
|
+
@generator =
|
117
|
+
case sym
|
103
118
|
|
104
|
-
|
105
|
-
|
106
|
-
|
119
|
+
# generates an Array containing shuffled integer values from 0 to size
|
120
|
+
when :array
|
121
|
+
Proc.new {|size| (0...size).to_a.shuffle }
|
107
122
|
|
108
|
-
|
109
|
-
|
123
|
+
# generates a random hex string of length size
|
124
|
+
when :string
|
125
|
+
Proc.new {|size| SecureRandom.hex(size) }
|
110
126
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
127
|
+
# simply returns the size
|
128
|
+
# for performance benefits when handling the
|
129
|
+
# size completely in the report block
|
130
|
+
when :size
|
131
|
+
Proc.new {|size| size }
|
115
132
|
|
116
|
-
|
117
|
-
|
118
|
-
@incrementer = blk
|
119
|
-
raise ArgumentError, "no block" unless @incrementer
|
120
|
-
end
|
133
|
+
# when :hash
|
134
|
+
# TODO: hash generator
|
121
135
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@logscale = false
|
136
|
+
else
|
137
|
+
raise "#{sym} is not a supported object type"
|
138
|
+
end
|
126
139
|
end
|
127
140
|
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
@
|
132
|
-
@logscale = true
|
141
|
+
# return the size for the nth step
|
142
|
+
# n = 0 returns @min_size
|
143
|
+
def step n
|
144
|
+
@min_size + (n * @step_size)
|
133
145
|
end
|
134
146
|
|
135
147
|
def sizes
|
136
|
-
|
137
|
-
|
148
|
+
@sizes ||=
|
149
|
+
(0...@steps).collect do |n|
|
150
|
+
step n
|
138
151
|
end
|
152
|
+
@sizes
|
139
153
|
end
|
140
154
|
|
141
155
|
def item label="", str=nil, &blk # :yield:
|
@@ -157,48 +171,61 @@ module Benchmark
|
|
157
171
|
end
|
158
172
|
alias_method :report, :item
|
159
173
|
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
174
|
+
def generate_output
|
175
|
+
generate_json
|
176
|
+
generate_csv
|
177
|
+
generate_chart
|
178
|
+
generate_termplot
|
165
179
|
end
|
166
180
|
|
167
|
-
def
|
168
|
-
return if @
|
181
|
+
def generate_json
|
182
|
+
return if @json_file.nil?
|
169
183
|
|
170
|
-
all_data = @full_report.
|
184
|
+
all_data = @full_report.data
|
171
185
|
|
172
|
-
File.open @
|
186
|
+
File.open @json_file, 'w' do |f|
|
173
187
|
f.write JSON.pretty_generate(all_data)
|
174
188
|
end
|
175
189
|
end
|
176
190
|
|
177
|
-
def
|
178
|
-
return if @
|
191
|
+
def generate_csv
|
192
|
+
return if @csv_file.nil?
|
179
193
|
|
180
|
-
all_data = @full_report.
|
194
|
+
all_data = @full_report.data
|
195
|
+
data_points = all_data.map{|report| report[:data].keys }.flatten.uniq
|
181
196
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
for chart_data in all_data
|
188
|
-
comparison_data = @full_report.comparison_chart_data chart_data, all_sizes
|
189
|
-
charts << { name: chart_data[:name], data: comparison_data, opts: @full_report.chart_opts(chart_data) }
|
197
|
+
CSV.open @csv_file, 'w' do |csv|
|
198
|
+
header = [''] + data_points
|
199
|
+
csv << header
|
200
|
+
all_data.each do |row|
|
201
|
+
csv << [row[:name]] + row[:data].values
|
190
202
|
end
|
191
203
|
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def generate_chart
|
207
|
+
return if @chart_file.nil?
|
208
|
+
|
209
|
+
@chart = Chart.new @full_report.data, sizes
|
210
|
+
|
211
|
+
charts = @chart.generate(compare: compare?)
|
192
212
|
|
193
213
|
template_file = File.join File.dirname(__FILE__), 'templates/chart.erb'
|
194
214
|
template = ERB.new(File.read(template_file))
|
195
215
|
|
196
|
-
File.open @
|
216
|
+
File.open @chart_file, 'w' do |f|
|
197
217
|
f.write template.result(binding)
|
198
218
|
end
|
199
219
|
|
200
220
|
end
|
201
221
|
|
222
|
+
def generate_termplot
|
223
|
+
return unless @term_plot
|
224
|
+
|
225
|
+
@plot = TermPlot.new @full_report.data, sizes
|
226
|
+
@plot.generate
|
227
|
+
end
|
228
|
+
|
202
229
|
end
|
203
230
|
end
|
204
231
|
end
|