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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ddf502a4bf2a5eabc6ec0127d22f1ab16ee69eca
4
- data.tar.gz: 43b1ce1e054c451c63a0d9597d73461d4b64be15
3
+ metadata.gz: f2001993a5384ac4c261dfcfab54718bd699ebe5
4
+ data.tar.gz: 9126a20c3623302fee2c997c2264d601164adc21
5
5
  SHA512:
6
- metadata.gz: 6c73e84f56907153c5ff2ec84ecf4da9942431ed477a2408504b0d96e2c5d0be2c3aee8638a117fa5c5da29a76b4c4b150f2221b5362520b66d296201dc19fce
7
- data.tar.gz: e7bbe74394cd62b596a51bc2002abe776f36c789ffb7b4a941dc88fb3e8c2fd2a4339dcc008c281e12264385b547fcfd91fd2d4c32a8e19d00405667eec57ea6
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
- report = Benchmark.bigo do |x|
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
- # specifies how the size of the object should grow
38
- # options: linear, exponential
39
- #
40
- # specifies that arrays should grow linearly by 1000
41
- x.linear 1000
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") {|generated, size| generated.at rand(size) }
46
- x.report("#index") {|generated, size| generated.index rand(size) }
47
- x.report("#empty-index") {|generated, size| generated.index 'foo' }
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:
@@ -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
@@ -35,16 +35,9 @@ module Benchmark
35
35
 
36
36
  include Chartkick::Helper
37
37
 
38
- # how many total increments are being measured
39
- attr_accessor :increments
38
+ require 'securerandom'
40
39
 
41
- # whether to graph the results on a log scale
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
- linear
57
- @increments = 5
58
- @logscale = false
59
- @chart = nil
60
- @data_file = nil
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
- @increments = opts[:increments] if opts[:increments]
66
- @logscale = opts[:logscale] if opts[:logscale]
67
- @full_report.logscale! if @logscale
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
- @chart
85
+ def chart! filename='chart.html'
86
+ @chart_file = filename
72
87
  end
73
88
 
74
- def chart! filename='chart.html'
75
- @chart = filename
89
+ def compare?
90
+ @compare
76
91
  end
77
92
 
78
- def data?
79
- @data_file
93
+ def termplot!
94
+ @term_plot = true
80
95
  end
81
96
 
82
- def data! filename='data.json'
83
- @data_file = filename
97
+ def json! filename='data.json'
98
+ @json_file = filename
84
99
  end
85
100
 
86
- def logscale= val
87
- @logscale = val
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
- case sym
116
+ @generator =
117
+ case sym
103
118
 
104
- # generates an Array containing shuffled integer values from 0 to size
105
- when :array
106
- @generator = Proc.new{|size| (0...size).to_a.shuffle }
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
- # when :string
109
- # to do
123
+ # generates a random hex string of length size
124
+ when :string
125
+ Proc.new {|size| SecureRandom.hex(size) }
110
126
 
111
- else
112
- raise "#{sym} is not a supported object type"
113
- end
114
- end
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
- # custom incrementer
117
- def incrementer &blk
118
- @incrementer = blk
119
- raise ArgumentError, "no block" unless @incrementer
120
- end
133
+ # when :hash
134
+ # TODO: hash generator
121
135
 
122
- # linear incrementer
123
- def linear increments=100
124
- @incrementer = Proc.new {|i| i * increments }
125
- @logscale = false
136
+ else
137
+ raise "#{sym} is not a supported object type"
138
+ end
126
139
  end
127
140
 
128
- # exponential incrementer
129
- def exponential base=10
130
- @incrementer = Proc.new {|i| base ** (i-1) }
131
- @full_report.logscale!
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
- (1..@increments).collect do |idx|
137
- @incrementer.call(idx).to_i
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 run_warmup
161
- super
162
-
163
- max_timing = @timing.values.max
164
- @full_report.per_iterations = 10**Math.log10(max_timing).ceil
174
+ def generate_output
175
+ generate_json
176
+ generate_csv
177
+ generate_chart
178
+ generate_termplot
165
179
  end
166
180
 
167
- def generate_data
168
- return if @data_file.nil?
181
+ def generate_json
182
+ return if @json_file.nil?
169
183
 
170
- all_data = @full_report.chart_data
184
+ all_data = @full_report.data
171
185
 
172
- File.open @data_file, 'w' do |f|
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 generate_chart
178
- return if @chart.nil?
191
+ def generate_csv
192
+ return if @csv_file.nil?
179
193
 
180
- all_data = @full_report.chart_data
194
+ all_data = @full_report.data
195
+ data_points = all_data.map{|report| report[:data].keys }.flatten.uniq
181
196
 
182
- charts = []
183
- charts << { name: 'Growth Chart', data: all_data, opts: @full_report.chart_opts(all_data) }
184
-
185
- if compare?
186
- all_sizes = sizes
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 @chart, 'w' do |f|
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