benchmarker 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 +7 -0
- data/CHANGES.md +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +107 -0
- data/benchmarker.gemspec +20 -34
- data/lib/benchmarker.rb +1041 -0
- data/test/benchmarker_test.rb +1532 -560
- metadata +53 -79
- data/CHANGES.txt +0 -6
- data/examples/bench_loop.rb +0 -51
- data/examples/bench_require.rb +0 -34
- data/test/oktest.rb +0 -825
data/lib/benchmarker.rb
ADDED
@@ -0,0 +1,1041 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
###
|
5
|
+
### $Release: 1.0.0 $
|
6
|
+
### $Copyright: copyright(c) 2010-2021 kuwata-lab.com all rights reserved $
|
7
|
+
### $License: MIT License $
|
8
|
+
###
|
9
|
+
|
10
|
+
|
11
|
+
module Benchmarker
|
12
|
+
|
13
|
+
|
14
|
+
VERSION = "$Release: 1.0.0 $".split()[1]
|
15
|
+
|
16
|
+
N_REPEAT = 100 # number of repeat (100 times)
|
17
|
+
|
18
|
+
OPTIONS = {} # ex: {loop: 1000, iter: 10, extra: 2, inverse: true}
|
19
|
+
|
20
|
+
def self.new(title=nil, **kwargs, &b)
|
21
|
+
#; [!s7y6x] overwrites existing options by command-line options.
|
22
|
+
kwargs.update(OPTIONS)
|
23
|
+
#; [!2zh7w] creates new Benchmark object wit options.
|
24
|
+
bm = Benchmark.new(title: title, **kwargs)
|
25
|
+
return bm
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.scope(title=nil, **kwargs, &block)
|
29
|
+
#; [!4f695] creates Benchmark object, define tasks, and run them.
|
30
|
+
bm = self.new(title, **kwargs)
|
31
|
+
bm.scope(&block)
|
32
|
+
bm.run()
|
33
|
+
return bm
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class Benchmark
|
38
|
+
|
39
|
+
def initialize(title: nil, width: 30, loop: 1, iter: 1, extra: 0, inverse: false, outfile: nil, quiet: false, colorize: nil, sleep: nil, filter: nil)
|
40
|
+
@title = title
|
41
|
+
@width = width || 30
|
42
|
+
@loop = loop || 1
|
43
|
+
@iter = iter || 1
|
44
|
+
@extra = extra || 0
|
45
|
+
@inverse = inverse || false
|
46
|
+
@outfile = outfile
|
47
|
+
@quiet = quiet || false
|
48
|
+
@colorize = colorize
|
49
|
+
@sleep = sleep
|
50
|
+
@filter = filter
|
51
|
+
if filter
|
52
|
+
#; [!0mz0f] error when filter string is invalid format.
|
53
|
+
filter =~ /^(task|tag)(!?=+)(.*)/ or
|
54
|
+
raise ArgumentError.new("#{filter}: invalid filter.")
|
55
|
+
#; [!xo7bq] error when filter operator is invalid.
|
56
|
+
$2 == '=' || $2 == '!=' or
|
57
|
+
raise ArgumentError.new("#{filter}: expected operator is '=' or '!='.")
|
58
|
+
end
|
59
|
+
@entries = [] # [[Task, Resutl]]
|
60
|
+
@jdata = {}
|
61
|
+
@hooks = {} # {before: Proc, after: Proc, ...}
|
62
|
+
@empty_task = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :title, :width, :loop, :iter, :extra, :inverse, :outfile, :quiet, :colorize, :sleep, :filter
|
66
|
+
|
67
|
+
def clear()
|
68
|
+
#; [!phqdn] clears benchmark result and JSON data.
|
69
|
+
@entries.each {|_, result| result.clear() }
|
70
|
+
@jdata = {}
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def scope(&block)
|
75
|
+
#; [!wrjy0] creates wrapper object and yields block with it as self.
|
76
|
+
#; [!6h24d] passes benchmark object as argument of block.
|
77
|
+
scope = Scope.new(self)
|
78
|
+
scope.instance_exec(self, &block)
|
79
|
+
#; [!y0uwr] returns self.
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def define_empty_task(code=nil, tag: nil, skip: nil, &block) # :nodoc:
|
84
|
+
#; [!qzr1s] error when called more than once.
|
85
|
+
@empty_task.nil? or
|
86
|
+
raise "cannot define empty task more than once."
|
87
|
+
#; [!w66xp] creates empty task.
|
88
|
+
@empty_task = TASK.new(nil, code, tag: tag, skip: skip, &block)
|
89
|
+
return @empty_task
|
90
|
+
end
|
91
|
+
|
92
|
+
def define_task(name, code=nil, tag: nil, skip: nil, &block) # :nodoc:
|
93
|
+
#; [!re6b8] creates new task.
|
94
|
+
#; [!r8o0p] can take a tag.
|
95
|
+
task = TASK.new(name, code, tag: tag, skip: skip, &block)
|
96
|
+
@entries << [task, Result.new]
|
97
|
+
return task
|
98
|
+
end
|
99
|
+
|
100
|
+
def define_hook(key, &block)
|
101
|
+
#; [!2u53t] register proc object with symbol key.
|
102
|
+
@hooks[key] = block
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def call_hook(key, *args)
|
107
|
+
#; [!0to2s] calls hook with arguments.
|
108
|
+
fn = @hooks[key]
|
109
|
+
fn.call(*args) if fn
|
110
|
+
end
|
111
|
+
private :call_hook
|
112
|
+
|
113
|
+
def run(warmup: false)
|
114
|
+
#; [!0fo0l] runs benchmark tasks and reports result.
|
115
|
+
report_environment()
|
116
|
+
filter_tasks()
|
117
|
+
#; [!2j4ks] calls 'before_all' hook.
|
118
|
+
call_hook(:before_all)
|
119
|
+
begin
|
120
|
+
if warmup
|
121
|
+
#; [!6h26u] runs preriminary round when `warmup: true` provided.
|
122
|
+
_ignore_output { invoke_tasks() }
|
123
|
+
clear()
|
124
|
+
end
|
125
|
+
invoke_tasks()
|
126
|
+
#; [!w1rq7] calls 'after_all' hook even if error raised.
|
127
|
+
ensure
|
128
|
+
call_hook(:after_all)
|
129
|
+
end
|
130
|
+
ignore_skipped_tasks()
|
131
|
+
report_minmax()
|
132
|
+
report_average()
|
133
|
+
report_stats()
|
134
|
+
write_outfile()
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def _ignore_output(&b)
|
141
|
+
#; [!wazs7] ignores output in block argument.
|
142
|
+
require 'stringio'
|
143
|
+
bkup, $stdout = $stdout, StringIO.new
|
144
|
+
begin
|
145
|
+
yield
|
146
|
+
ensure
|
147
|
+
$stdout = bkup
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def filter_tasks()
|
152
|
+
#; [!g207d] do nothing when filter string is not provided.
|
153
|
+
if @filter
|
154
|
+
#; [!f1n1v] filters tasks by task name when filer string is 'task=...'.
|
155
|
+
#; [!m79cf] filters tasks by tag value when filer string is 'tag=...'.
|
156
|
+
@filter =~ /^(task|tag)(=|!=)(.*)/ or raise "** internal error"
|
157
|
+
key = $1; op = $2; pattern = $3
|
158
|
+
@entries = @entries.select {|task, _|
|
159
|
+
val = key == 'tag' ? task.tag : task.name
|
160
|
+
if val
|
161
|
+
bool = [val].flatten.any? {|v| File.fnmatch(pattern, v, File::FNM_EXTGLOB) }
|
162
|
+
else
|
163
|
+
bool = false
|
164
|
+
end
|
165
|
+
#; [!0in0q] supports negative filter by '!=' operator.
|
166
|
+
op == '!=' ? !bool : bool
|
167
|
+
}
|
168
|
+
end
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def invoke_tasks()
|
173
|
+
@jdata[:Results] = []
|
174
|
+
#; [!c8yak] invokes tasks once if 'iter' option not specified.
|
175
|
+
#; [!unond] invokes tasks multiple times if 'iter' option specified.
|
176
|
+
#; [!wzvdb] invokes tasks 16 times if 'iter' is 10 and 'extra' is 3.
|
177
|
+
n = @iter + 2 * @extra
|
178
|
+
(1..n).each do |i|
|
179
|
+
@jdata[:Results] << (rows = [])
|
180
|
+
#; [!5axhl] prints result even on quiet mode if no 'iter' nor 'extra'.
|
181
|
+
quiet = @quiet && n != 1
|
182
|
+
#; [!yg9i7] prints result unless quiet mode.
|
183
|
+
#; [!94916] suppresses result if quiet mode.
|
184
|
+
#heading = n == 1 ? "##" : "## (##{i})"
|
185
|
+
if n == 1
|
186
|
+
heading = "##"
|
187
|
+
space = " " * (@width - heading.length)
|
188
|
+
else
|
189
|
+
heading = "## " + colorize_iter("(##{i})")
|
190
|
+
space = " " * (@width - "## (##{i})".length)
|
191
|
+
end
|
192
|
+
puts "" unless quiet
|
193
|
+
#puts "%-#{@width}s %9s %9s %9s %9s" % [heading, 'user', 'sys', 'total', 'real'] unless quiet
|
194
|
+
puts "%s%s %9s %9s %9s %9s" % [heading, space, 'user', 'sys', 'total', 'real'] unless quiet
|
195
|
+
#; [!3hgos] invokes empty task at first if defined.
|
196
|
+
if @empty_task && !@empty_task.skip?
|
197
|
+
empty_timeset = __invoke(@empty_task, "(Empty)", nil, quiet)
|
198
|
+
t = empty_timeset
|
199
|
+
s = "%9.4f %9.4f %9.4f %9.4f" % [t.user, t.sys, t.total, t.real]
|
200
|
+
#s = "%9.4f %9.4f %9.4f %s" % [t.user, t.sys, t.total, colorize_real('%9.4f' % t.real)]
|
201
|
+
puts s unless quiet
|
202
|
+
#; [!knjls] records result of empty loop into JSON data.
|
203
|
+
rows << ["(Empty)"] + empty_timeset.to_a.collect {|x| ('%9.4f' % x).to_f }
|
204
|
+
Kernel.sleep @sleep if @sleep
|
205
|
+
else
|
206
|
+
empty_timeset = nil
|
207
|
+
end
|
208
|
+
#; [!xf84h] invokes all tasks.
|
209
|
+
@entries.each do |task, result|
|
210
|
+
timeset = __invoke(task, task.name, @hooks[:validate], quiet)
|
211
|
+
#; [!dyofw] prints reason if 'skip:' option specified.
|
212
|
+
if task.skip?
|
213
|
+
reason = task.skip
|
214
|
+
result.skipped = reason
|
215
|
+
puts " # Skipped (reason: #{reason})" unless quiet
|
216
|
+
#; [!ygpx0] records reason of skip into JSON data.
|
217
|
+
rows << [task.name, nil, nil, nil, nil, reason]
|
218
|
+
next
|
219
|
+
end
|
220
|
+
#; [!513ok] subtract timeset of empty loop from timeset of each task.
|
221
|
+
if empty_timeset
|
222
|
+
timeset -= empty_timeset unless task.has_code?
|
223
|
+
timeset -= empty_timeset.div(N_REPEAT) if task.has_code?
|
224
|
+
end
|
225
|
+
t = timeset
|
226
|
+
#s = "%9.4f %9.4f %9.4f %9.4f" % [t.user, t.sys, t.total, t.real]
|
227
|
+
s = "%9.4f %9.4f %9.4f %s" % [t.user, t.sys, t.total, colorize_real('%9.4f' % t.real)]
|
228
|
+
puts s unless quiet
|
229
|
+
result.add(timeset)
|
230
|
+
#; [!ejxif] records result of each task into JSON data.
|
231
|
+
rows << [task.name] + timeset.to_a.collect {|x| ('%9.4f' % x).to_f }
|
232
|
+
#; [!vbhvz] sleeps N seconds after each task if `sleep` option specified.
|
233
|
+
Kernel.sleep @sleep if @sleep
|
234
|
+
end
|
235
|
+
end
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
def __invoke(task, task_name, validator, quiet)
|
239
|
+
print "%-#{@width}s " % task_name unless quiet
|
240
|
+
$stdout.flush() unless quiet
|
241
|
+
#; [!fv4cv] skips task invocation if skip reason is specified.
|
242
|
+
return nil if task.skip?
|
243
|
+
#; [!hbass] calls 'before' hook with task name and tag.
|
244
|
+
call_hook(:before, task.name, task.tag)
|
245
|
+
#; [!6g36c] invokes task with validator if validator defined.
|
246
|
+
begin
|
247
|
+
timeset = task.invoke(@loop, &validator)
|
248
|
+
return timeset
|
249
|
+
#; [!7960c] calls 'after' hook with task name and tag even if error raised.
|
250
|
+
ensure
|
251
|
+
call_hook(:after, task_name, task.tag)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def ignore_skipped_tasks()
|
256
|
+
#; [!5gpo7] removes skipped tasks and leaves other tasks.
|
257
|
+
@entries = @entries.reject {|_, result| result.skipped? }
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
|
261
|
+
def report_environment()
|
262
|
+
#; [!rx7nn] prints ruby version, platform, several options, and so on.
|
263
|
+
s = "loop=#{@loop.inspect}, iter=#{@iter.inspect}, extra=#{@extra.inspect}"
|
264
|
+
s += ", inverse=#{@inverse}" if @inverse
|
265
|
+
kvs = [["title", @title], ["options", s]] + Misc.environment_info()
|
266
|
+
puts kvs.collect {|k, v| "## %-16s %s\n" % ["#{k}:", v] }.join()
|
267
|
+
@jdata[:Environment] = Hash.new(kvs)
|
268
|
+
nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def report_minmax()
|
272
|
+
if @extra > 0
|
273
|
+
rows = _remove_minmax()
|
274
|
+
puts _render_minmax(rows)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def _remove_minmax()
|
279
|
+
#; [!uxe7e] removes best and worst results if 'extra' option specified.
|
280
|
+
tuples = []
|
281
|
+
@entries.each do |task, result|
|
282
|
+
removed_list = result.remove_minmax(@extra)
|
283
|
+
tuples << [task.name, removed_list]
|
284
|
+
end
|
285
|
+
#; [!is6ll] returns removed min and max data.
|
286
|
+
rows = []
|
287
|
+
tuples.each do |task_name, removed_list|
|
288
|
+
removed_list.each_with_index do |(min_t, min_idx, max_t, max_idx), i|
|
289
|
+
task_name = nil if i > 0
|
290
|
+
min_t2 = ("%9.4f" % min_t).to_f
|
291
|
+
max_t2 = ("%9.4f" % max_t).to_f
|
292
|
+
rows << [task_name, min_t2, "(##{min_idx})", max_t2, "(##{max_idx})"]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
#; [!xwddz] sets removed best and worst results into JSON data.
|
296
|
+
@jdata[:RemovedMinMax] = rows
|
297
|
+
return rows
|
298
|
+
end
|
299
|
+
|
300
|
+
def _render_minmax(rows)
|
301
|
+
#; [!p71ax] returns rendered string.
|
302
|
+
buf = ["\n"]
|
303
|
+
heading = "## Removed Min & Max"
|
304
|
+
buf << "%-#{@width+4}s %5s %9s %9s %9s\n" % [heading, 'min', 'iter', 'max', 'iter']
|
305
|
+
rows.each do |row|
|
306
|
+
#buf << "%-#{@width}s %9.4f %9s %9.4f %9s\n" % row
|
307
|
+
task_name, min_t, min_i, max_t, max_i = row
|
308
|
+
arr = [task_name, colorize_real('%9.4f' % min_t), colorize_iter('%9s' % min_i),
|
309
|
+
colorize_real('%9.4f' % max_t), colorize_iter('%9s' % max_i)]
|
310
|
+
buf << "%-#{@width}s %s %s %s %s\n" % arr
|
311
|
+
end
|
312
|
+
return buf.join()
|
313
|
+
end
|
314
|
+
|
315
|
+
def report_average()
|
316
|
+
if @iter > 1 || @extra > 0
|
317
|
+
rows = _calc_average()
|
318
|
+
puts _render_average(rows)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def _calc_average()
|
323
|
+
#; [!qu29s] calculates average of real times for each task.
|
324
|
+
rows = @entries.collect {|task, result|
|
325
|
+
avg_timeset = result.calc_average()
|
326
|
+
[task.name] + avg_timeset.to_a.collect {|x| ("%9.4f" % x).to_f }
|
327
|
+
}
|
328
|
+
#; [!jxf28] sets average results into JSON data.
|
329
|
+
@jdata[:Average] = rows
|
330
|
+
return rows
|
331
|
+
end
|
332
|
+
|
333
|
+
def _render_average(rows)
|
334
|
+
#; [!j9wlv] returns rendered string.
|
335
|
+
buf = ["\n"]
|
336
|
+
heading = "## Average of #{@iter}"
|
337
|
+
heading += " (=#{@iter + 2 * @extra}-2*#{@extra})" if @extra > 0
|
338
|
+
buf << "%-#{@width+4}s %5s %9s %9s %9s\n" % [heading, 'user', 'sys', 'total', 'real']
|
339
|
+
rows.each do |row|
|
340
|
+
#buf << "%-#{@width}s %9.4f %9.4f %9.4f %9.4f\n" % row
|
341
|
+
real = colorize_real('%9.4f' % row.pop())
|
342
|
+
buf << "%-#{@width}s %9.4f %9.4f %9.4f %s\n" % (row + [real])
|
343
|
+
end
|
344
|
+
return buf.join()
|
345
|
+
end
|
346
|
+
|
347
|
+
def report_stats()
|
348
|
+
#; [!0jn7d] sorts results by real sec.
|
349
|
+
pairs = @entries.collect {|task, result|
|
350
|
+
#real = @iter > 1 || @extra > 0 ? result.calc_average().real : result[0].real
|
351
|
+
real = result.calc_average().real
|
352
|
+
[task.name, real]
|
353
|
+
}
|
354
|
+
pairs = pairs.sort_by {|_, real| real }
|
355
|
+
print _render_ranking(pairs)
|
356
|
+
print _render_matrix(pairs)
|
357
|
+
end
|
358
|
+
|
359
|
+
def _render_ranking(pairs)
|
360
|
+
#; [!2lu55] calculates ranking data and sets it into JSON data.
|
361
|
+
rows = []
|
362
|
+
base = nil
|
363
|
+
pairs.each do |task_name, sec|
|
364
|
+
base ||= sec
|
365
|
+
percent = 100.0 * base / sec
|
366
|
+
barchart = '*' * (percent / 5.0).round() # max 20 chars (=100%)
|
367
|
+
loop = @inverse == true ? (@loop || 1) : (@inverse || @loop || 1)
|
368
|
+
rows << [task_name, ("%.4f" % sec).to_f, "%.1f%%" % percent,
|
369
|
+
"%.2f times/sec" % (loop / sec), barchart]
|
370
|
+
end
|
371
|
+
@jdata[:Ranking] = rows
|
372
|
+
#; [!55x8r] returns rendered string of ranking.
|
373
|
+
buf = ["\n"]
|
374
|
+
heading = "## Ranking"
|
375
|
+
if @inverse
|
376
|
+
buf << "%-#{@width}s %9s%30s\n" % [heading, 'real', 'times/sec']
|
377
|
+
else
|
378
|
+
buf << "%-#{@width}s %9s\n" % [heading, 'real']
|
379
|
+
end
|
380
|
+
rows.each do |task_name, sec, percent, inverse, barchart|
|
381
|
+
s = @inverse ? "%20s" % inverse.split()[0] : barchart
|
382
|
+
#buf << "%-#{@width}s %9.4f (%6s) %s\n" % [task_name, sec, percent, s]
|
383
|
+
buf << "%-#{@width}s %s (%6s) %s\n" % [task_name, colorize_real('%9.4f' % sec), percent, s]
|
384
|
+
end
|
385
|
+
return buf.join()
|
386
|
+
end
|
387
|
+
|
388
|
+
def _render_matrix(pairs)
|
389
|
+
#; [!2lu55] calculates ranking data and sets it into JSON data.
|
390
|
+
rows = []
|
391
|
+
pairs.each_with_index do |(task_name, sec), i|
|
392
|
+
base = pairs[i][1]
|
393
|
+
row = ["[#{i+1}] #{task_name}", ("%9.4f" % sec).to_f]
|
394
|
+
pairs.each {|_, r| row << "%.1f%%" % (100.0 * r / base) }
|
395
|
+
rows << row
|
396
|
+
end
|
397
|
+
@jdata[:Matrix] = rows
|
398
|
+
#; [!rwfxu] returns rendered string of matrix.
|
399
|
+
buf = ["\n"]
|
400
|
+
heading = "## Matrix"
|
401
|
+
s = "%-#{@width}s %9s" % [heading, 'real']
|
402
|
+
(1..pairs.length).each {|i| s += " %8s" % "[#{i}]" }
|
403
|
+
buf << "#{s}\n"
|
404
|
+
rows.each do |task_name, real, *percents|
|
405
|
+
s = "%-#{@width}s %s" % [task_name, colorize_real('%9.4f' % real)]
|
406
|
+
percents.each {|p| s += " %8s" % p }
|
407
|
+
buf << "#{s}\n"
|
408
|
+
end
|
409
|
+
return buf.join()
|
410
|
+
end
|
411
|
+
|
412
|
+
def write_outfile()
|
413
|
+
#; [!o8ah6] writes result data into JSON file if 'outfile' option specified.
|
414
|
+
if @outfile
|
415
|
+
filename = @outfile
|
416
|
+
require 'json'
|
417
|
+
jstr = JSON.pretty_generate(@jdata, indent: ' ', space: ' ')
|
418
|
+
if filename == '-'
|
419
|
+
$stdout.puts(jstr)
|
420
|
+
else
|
421
|
+
File.write(filename, jstr)
|
422
|
+
end
|
423
|
+
jstr
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def colorize?
|
428
|
+
#; [!cy10n] returns true if '-c' option specified.
|
429
|
+
#; [!e0gcz] returns false if '-C' option specified.
|
430
|
+
#; [!6v90d] returns result of `Color.colorize?` if neither '-c' nor '-C' specified.
|
431
|
+
return @colorize.nil? ? Color.colorize?() : @colorize
|
432
|
+
end
|
433
|
+
|
434
|
+
def colorize_real(s)
|
435
|
+
colorize?() ? Color.real(s) : s
|
436
|
+
end
|
437
|
+
|
438
|
+
def colorize_iter(s)
|
439
|
+
colorize?() ? Color.iter(s) : s
|
440
|
+
end
|
441
|
+
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
class Scope
|
446
|
+
|
447
|
+
def initialize(bm=nil)
|
448
|
+
@__bm = bm
|
449
|
+
end
|
450
|
+
|
451
|
+
def task(name, code=nil, binding=nil, tag: nil, skip: nil, &block)
|
452
|
+
#; [!843ju] when code argument provided...
|
453
|
+
if code
|
454
|
+
#; [!bwfak] code argument and block argument are exclusive.
|
455
|
+
! block_given?() or
|
456
|
+
raise TaskError, "task(#{name.inspect}): cannot accept #{code.class} argument when block argument given."
|
457
|
+
#; [!4dm9q] generates block argument if code argument passed.
|
458
|
+
location = caller_locations(1, 1).first
|
459
|
+
defcode = "proc do #{(code+';') * N_REPEAT} end" # repeat code 100 times
|
460
|
+
binding ||= ::TOPLEVEL_BINDING
|
461
|
+
block = eval defcode, binding, location.path, location.lineno+1
|
462
|
+
end
|
463
|
+
#; [!kh7r9] define empty-loop task if name is nil.
|
464
|
+
return @__bm.define_empty_task(code, tag: tag, skip: skip, &block) if name.nil?
|
465
|
+
#; [!j6pmr] creates new task object.
|
466
|
+
return @__bm.define_task(name, code, tag: tag, skip: skip, &block)
|
467
|
+
end
|
468
|
+
alias report task # for compatibility with 'benchamrk.rb'
|
469
|
+
|
470
|
+
def empty_task(code=nil, binding=nil, &block)
|
471
|
+
#; [!ycoch] creates new empty-loop task object.
|
472
|
+
return task(nil, code, binding, &block)
|
473
|
+
end
|
474
|
+
|
475
|
+
def assert_eq(actual, expected, errmsg=nil)
|
476
|
+
#; [!8m6bh] do nothing if ectual == expected.
|
477
|
+
#; [!f9ey6] raises error unless actual == expected.
|
478
|
+
return if actual == expected
|
479
|
+
errmsg ||= "#{actual.inspect} == #{expected.inspect}: failed."
|
480
|
+
assert false, errmsg
|
481
|
+
end
|
482
|
+
|
483
|
+
def before(&block)
|
484
|
+
#; [!2ir4q] defines 'before' hook.
|
485
|
+
@__bm.define_hook(:before, &block)
|
486
|
+
end
|
487
|
+
|
488
|
+
def after(&block)
|
489
|
+
#; [!05up6] defines 'after' hook.
|
490
|
+
@__bm.define_hook(:after, &block)
|
491
|
+
end
|
492
|
+
|
493
|
+
def before_all(&block)
|
494
|
+
#; [!1oier] defines 'before_all' hook.
|
495
|
+
@__bm.define_hook(:before_all, &block)
|
496
|
+
end
|
497
|
+
|
498
|
+
def after_all(&block)
|
499
|
+
#; [!z7xop] defines 'after_all' hook.
|
500
|
+
@__bm.define_hook(:after_all, &block)
|
501
|
+
end
|
502
|
+
|
503
|
+
def validate(&block)
|
504
|
+
#; [!q2aev] defines validator.
|
505
|
+
return @__bm.define_hook(:validate, &block)
|
506
|
+
end
|
507
|
+
|
508
|
+
def assert(expr, errmsg)
|
509
|
+
#; [!a0c7e] do nothing if assertion succeeded.
|
510
|
+
#; [!5vmbc] raises error if assertion failed.
|
511
|
+
#; [!7vt5l] puts newline if assertion failed.
|
512
|
+
return if expr
|
513
|
+
puts ""
|
514
|
+
raise ValidationFailed, errmsg
|
515
|
+
rescue => exc
|
516
|
+
#; [!mhw59] makes error backtrace compact.
|
517
|
+
exc.backtrace.reject! {|x| x =~ /[\/\\:]benchmarker\.rb.*:/ }
|
518
|
+
raise
|
519
|
+
end
|
520
|
+
|
521
|
+
end
|
522
|
+
|
523
|
+
|
524
|
+
class ValidationFailed < StandardError
|
525
|
+
end
|
526
|
+
|
527
|
+
|
528
|
+
class TaskError < StandardError
|
529
|
+
end
|
530
|
+
|
531
|
+
|
532
|
+
class Task
|
533
|
+
|
534
|
+
def initialize(name, code=nil, tag: nil, skip: nil, &block)
|
535
|
+
@name = name
|
536
|
+
@code = code
|
537
|
+
@tag = tag
|
538
|
+
@skip = skip # reason to skip
|
539
|
+
@block = block
|
540
|
+
end
|
541
|
+
|
542
|
+
attr_reader :name, :tag, :skip, :block
|
543
|
+
|
544
|
+
def has_code?
|
545
|
+
return !!@code
|
546
|
+
end
|
547
|
+
|
548
|
+
def skip?
|
549
|
+
return !!@skip
|
550
|
+
end
|
551
|
+
|
552
|
+
def invoke(loop=1, &validator)
|
553
|
+
#; [!s2f6v] when task block is build from repeated code...
|
554
|
+
if @code
|
555
|
+
n_repeat = N_REPEAT # == 100
|
556
|
+
#; [!i2r8o] error when number of loop is less than 100.
|
557
|
+
loop >= n_repeat or
|
558
|
+
raise TaskError, "task(#{@name.inspect}): number of loop (=#{loop}) should be >= #{n_repeat}, but not."
|
559
|
+
#; [!kzno6] error when number of loop is not a multiple of 100.
|
560
|
+
loop % n_repeat == 0 or
|
561
|
+
raise TaskError, "task(#{@name.inspect}): number of loop (=#{loop}) should be a multiple of #{n_repeat}, but not."
|
562
|
+
#; [!gbukv] changes number of loop to 1/100.
|
563
|
+
loop = loop / n_repeat
|
564
|
+
end
|
565
|
+
#; [!frq25] kicks GC before calling task block.
|
566
|
+
GC.start()
|
567
|
+
#; [!tgql6] invokes block N times.
|
568
|
+
block = @block
|
569
|
+
t1 = Process.times
|
570
|
+
start_t = Time.now
|
571
|
+
while (loop -= 1) >= 0
|
572
|
+
ret = block.call()
|
573
|
+
end
|
574
|
+
end_t = Time.now
|
575
|
+
t2 = Process.times
|
576
|
+
#; [!zw4kt] yields validator with result value of block.
|
577
|
+
yield ret, @name, @tag if block_given?()
|
578
|
+
#; [!9e5pr] returns TimeSet object.
|
579
|
+
user = t2.utime - t1.utime
|
580
|
+
sys = t2.stime - t1.stime
|
581
|
+
total = user + sys
|
582
|
+
real = end_t - start_t
|
583
|
+
return TimeSet.new(user, sys, total, real)
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
587
|
+
|
588
|
+
TASK = Task
|
589
|
+
|
590
|
+
|
591
|
+
TimeSet = Struct.new('TimeSet', :user, :sys, :total, :real) do
|
592
|
+
|
593
|
+
def -(t)
|
594
|
+
#; [!cpwgf] returns new TimeSet object.
|
595
|
+
user = self.user - t.user
|
596
|
+
sys = self.sys - t.sys
|
597
|
+
total = self.total - t.total
|
598
|
+
real = self.real - t.real
|
599
|
+
return TimeSet.new(user, sys, total, real)
|
600
|
+
end
|
601
|
+
|
602
|
+
def div(n)
|
603
|
+
#; [!4o9ns] returns new TimeSet object which values are divided by n.
|
604
|
+
user = self.user / n
|
605
|
+
sys = self.sys / n
|
606
|
+
total = self.total / n
|
607
|
+
real = self.real / n
|
608
|
+
return TimeSet.new(user, sys, total, real)
|
609
|
+
end
|
610
|
+
|
611
|
+
end
|
612
|
+
|
613
|
+
|
614
|
+
class Result
|
615
|
+
|
616
|
+
def initialize()
|
617
|
+
@iterations = []
|
618
|
+
end
|
619
|
+
|
620
|
+
def [](idx)
|
621
|
+
return @iterations[idx]
|
622
|
+
end
|
623
|
+
|
624
|
+
def length()
|
625
|
+
return @iterations.length
|
626
|
+
end
|
627
|
+
|
628
|
+
def each(&b)
|
629
|
+
@iterations.each(&b)
|
630
|
+
end
|
631
|
+
|
632
|
+
def add(timeset)
|
633
|
+
#; [!thyms] adds timeset and returns self.
|
634
|
+
@iterations << timeset
|
635
|
+
self
|
636
|
+
end
|
637
|
+
|
638
|
+
def clear()
|
639
|
+
#; [!fxrn6] clears timeset array.
|
640
|
+
@iterations.clear()
|
641
|
+
self
|
642
|
+
end
|
643
|
+
|
644
|
+
def skipped=(reason)
|
645
|
+
@reason = reason
|
646
|
+
end
|
647
|
+
|
648
|
+
def skipped?
|
649
|
+
#; [!bvzk9] returns true if reason has set, or returns false.
|
650
|
+
return !!@reason
|
651
|
+
end
|
652
|
+
|
653
|
+
def remove_minmax(extra, key=:real)
|
654
|
+
#; [!b55zh] removes best and worst timeset and returns them.
|
655
|
+
i = 0
|
656
|
+
pairs = @iterations.collect {|t| [t, i+=1] }
|
657
|
+
pairs = pairs.sort_by {|pair| pair[0].__send__(key) }
|
658
|
+
removed = []
|
659
|
+
extra.times do
|
660
|
+
min_timeset, min_idx = pairs.shift()
|
661
|
+
max_timeset, max_idx = pairs.pop()
|
662
|
+
min_t = min_timeset.__send__(key)
|
663
|
+
max_t = max_timeset.__send__(key)
|
664
|
+
removed << [min_t, min_idx, max_t, max_idx]
|
665
|
+
end
|
666
|
+
remained = pairs.sort_by {|_, i| i }.collect {|t, _| t }
|
667
|
+
@iterations = remained
|
668
|
+
return removed
|
669
|
+
end
|
670
|
+
|
671
|
+
def calc_average()
|
672
|
+
#; [!b91w3] returns average of timeddata.
|
673
|
+
user = sys = total = real = 0.0
|
674
|
+
@iterations.each do |t|
|
675
|
+
user += t.user
|
676
|
+
sys += t.sys
|
677
|
+
total += t.total
|
678
|
+
real += t.real
|
679
|
+
end
|
680
|
+
n = @iterations.length
|
681
|
+
return TimeSet.new(user/n, sys/n, total/n, real/n)
|
682
|
+
end
|
683
|
+
|
684
|
+
end
|
685
|
+
|
686
|
+
|
687
|
+
module Misc
|
688
|
+
|
689
|
+
module_function
|
690
|
+
|
691
|
+
def environment_info()
|
692
|
+
#; [!w1xfa] returns environment info in key-value list.
|
693
|
+
ruby_engine_version = (RUBY_ENGINE_VERSION rescue nil)
|
694
|
+
cc_version_msg = RbConfig::CONFIG['CC_VERSION_MESSAGE']
|
695
|
+
return [
|
696
|
+
["benchmarker" , "release #{VERSION}"],
|
697
|
+
["ruby engine" , "#{RUBY_ENGINE} (engine version #{ruby_engine_version})"],
|
698
|
+
["ruby version" , "#{RUBY_VERSION} (patch level #{RUBY_PATCHLEVEL})"],
|
699
|
+
["ruby platform" , RUBY_PLATFORM],
|
700
|
+
["ruby path" , RbConfig.ruby],
|
701
|
+
["compiler" , cc_version_msg ? cc_version_msg.split(/\r?\n/)[0] : nil],
|
702
|
+
["os name" , os_name()],
|
703
|
+
["cpu model" , cpu_model()],
|
704
|
+
]
|
705
|
+
end
|
706
|
+
|
707
|
+
def os_name()
|
708
|
+
#; [!83vww] returns string representing os name.
|
709
|
+
if File.file?("/usr/bin/sw_vers") # macOS
|
710
|
+
s = `/usr/bin/sw_vers`
|
711
|
+
s =~ /^ProductName:\s+(.*)/; product = $1
|
712
|
+
s =~ /^ProductVersion:\s+(.*)/; version = $1
|
713
|
+
return "#{product} #{version}"
|
714
|
+
end
|
715
|
+
if File.file?("/etc/lsb-release") # Linux
|
716
|
+
s = File.read("/etc/lsb-release", encoding: 'utf-8')
|
717
|
+
if s =~ /^DISTRIB_DESCRIPTION="(.*)"/
|
718
|
+
return $1
|
719
|
+
end
|
720
|
+
end
|
721
|
+
if File.file?("/usr/bin/uname") # UNIX
|
722
|
+
s = `/usr/bin/uname -srm`
|
723
|
+
return s.strip
|
724
|
+
end
|
725
|
+
if RUBY_PLATFORM =~ /win/ # Windows
|
726
|
+
s = `systeminfo` # TODO: not tested yet
|
727
|
+
s =~ /^OS Name:\s+(?:Microsft )?(.*)/; product = $1
|
728
|
+
s =~ /^OS Version:\s+(.*)/; version = $1 ? $1.split()[0] : nil
|
729
|
+
return "#{product} #{version}"
|
730
|
+
end
|
731
|
+
return nil
|
732
|
+
end
|
733
|
+
|
734
|
+
def cpu_model()
|
735
|
+
#; [!6ncgq] returns string representing cpu model.
|
736
|
+
if File.exist?("/usr/sbin/sysctl") # macOS
|
737
|
+
s = `/usr/sbin/sysctl machdep.cpu.brand_string`
|
738
|
+
s =~ /^machdep\.cpu\.brand_string: (.*)/
|
739
|
+
return $1
|
740
|
+
elsif File.exist?("/proc/cpuinfo") # Linux
|
741
|
+
s = `cat /proc/cpuinfo`
|
742
|
+
s =~ /^model name\s*: (.*)/
|
743
|
+
return $1
|
744
|
+
elsif File.exist?("/var/run/dmesg.boot") # FreeBSD
|
745
|
+
s = `grep ^CPU: /var/run/dmesg.boot`
|
746
|
+
s =~ /^CPU: (.*)/
|
747
|
+
return $1
|
748
|
+
elsif RUBY_PLATFORM =~ /win/ # Windows
|
749
|
+
s = `systeminfo`
|
750
|
+
s =~ /^\s+\[01\]: (.*)/ # TODO: not tested yet
|
751
|
+
return $1
|
752
|
+
else
|
753
|
+
return nil
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
def sample_code()
|
758
|
+
return <<'END'
|
759
|
+
# -*- coding: utf-8 -*-
|
760
|
+
|
761
|
+
require 'benchmarker' # https://kwatch.github.io/benchmarker/ruby.html
|
762
|
+
|
763
|
+
nums = (1..10000).to_a
|
764
|
+
|
765
|
+
title = "calculate sum of integers"
|
766
|
+
Benchmarker.scope(title, width: 24, loop: 1000, iter: 5, extra: 1) do
|
767
|
+
## other options -- inverse: true, outfile: "result.json", quiet: true,
|
768
|
+
## sleep: 1, colorize: true, filter: "task=*foo*"
|
769
|
+
|
770
|
+
## hooks
|
771
|
+
#before_all do end
|
772
|
+
#after_all do end
|
773
|
+
#before do end # or: before do |task_name, tag| end
|
774
|
+
#after do end # or: after do |task_name, tag| end
|
775
|
+
|
776
|
+
## tasks
|
777
|
+
task nil do # empty-loop task
|
778
|
+
# do nothing
|
779
|
+
end
|
780
|
+
|
781
|
+
task "each() & '+='" do
|
782
|
+
total = 0
|
783
|
+
nums.each {|n| total += n }
|
784
|
+
total
|
785
|
+
end
|
786
|
+
|
787
|
+
task "inject()" do
|
788
|
+
total = nums.inject(0) {|t, n| t += n }
|
789
|
+
total
|
790
|
+
end
|
791
|
+
|
792
|
+
task "while statement" do
|
793
|
+
total = 0; i = -1; len = nums.length
|
794
|
+
while (i += 1) < len
|
795
|
+
total += nums[i]
|
796
|
+
end
|
797
|
+
total
|
798
|
+
end
|
799
|
+
|
800
|
+
#task "name", tag: "curr", skip: (!condition ? nil : "...reason...") do
|
801
|
+
# ... run benchmark code ...
|
802
|
+
#end
|
803
|
+
|
804
|
+
## validation
|
805
|
+
validate do |val| # or: validate do |val, task_name, tag|
|
806
|
+
n = nums.last
|
807
|
+
expected = n * (n+1) / 2
|
808
|
+
assert_eq val, expected
|
809
|
+
# or: assert val == expected, "expected #{expected} but got #{val}"
|
810
|
+
end
|
811
|
+
|
812
|
+
end
|
813
|
+
END
|
814
|
+
end
|
815
|
+
|
816
|
+
end
|
817
|
+
|
818
|
+
|
819
|
+
module Color
|
820
|
+
|
821
|
+
module_function
|
822
|
+
|
823
|
+
def black(s); "\e[0;30m#{s}\e[0m"; end
|
824
|
+
def red(s); "\e[0;31m#{s}\e[0m"; end
|
825
|
+
def green(s); "\e[0;32m#{s}\e[0m"; end
|
826
|
+
def yellow(s); "\e[0;33m#{s}\e[0m"; end
|
827
|
+
def blue(s); "\e[0;34m#{s}\e[0m"; end
|
828
|
+
def magenta(s); "\e[0;35m#{s}\e[0m"; end
|
829
|
+
def cyan(s); "\e[0;36m#{s}\e[0m"; end
|
830
|
+
def white(s); "\e[0;37m#{s}\e[0m"; end
|
831
|
+
|
832
|
+
class << self
|
833
|
+
alias real cyan
|
834
|
+
alias iter magenta
|
835
|
+
end
|
836
|
+
|
837
|
+
def colorize?
|
838
|
+
#; [!fc741] returns true if stdout is a tty, else returns false.
|
839
|
+
return $stdout.tty?
|
840
|
+
end
|
841
|
+
|
842
|
+
end
|
843
|
+
|
844
|
+
|
845
|
+
class OptionParser
|
846
|
+
|
847
|
+
def initialize(opts_noparam, opts_hasparam, opts_mayparam="")
|
848
|
+
@opts_noparam = opts_noparam
|
849
|
+
@opts_hasparam = opts_hasparam
|
850
|
+
@opts_mayparam = opts_mayparam
|
851
|
+
end
|
852
|
+
|
853
|
+
def parse(argv)
|
854
|
+
#; [!2gq7g] returns options and keyvals.
|
855
|
+
options = {}; keyvals = {}
|
856
|
+
while !argv.empty? && argv[0] =~ /^-/
|
857
|
+
argstr = argv.shift
|
858
|
+
case argstr
|
859
|
+
#; [!ulfpu] stops parsing when '--' found.
|
860
|
+
when '--'
|
861
|
+
break
|
862
|
+
#; [!8f085] regards '--long=option' as key-value.
|
863
|
+
when /^--/
|
864
|
+
argstr =~ /^--(\w[-\w]*)(?:=(.*))?$/ or
|
865
|
+
yield "#{argstr}: invalid option."
|
866
|
+
key = $1; val = $2
|
867
|
+
keyvals[key] = val || true
|
868
|
+
#; [!dkq1u] parses short options.
|
869
|
+
when /^-/
|
870
|
+
i = 1
|
871
|
+
while i < argstr.length
|
872
|
+
c = argstr[i]
|
873
|
+
if @opts_noparam.include?(c)
|
874
|
+
options[c] = true
|
875
|
+
i += 1
|
876
|
+
elsif @opts_hasparam.include?(c)
|
877
|
+
#; [!8xqla] error when required argument is not provided.
|
878
|
+
options[c] = i+1 < argstr.length ? argstr[(i+1)..-1] : argv.shift() or
|
879
|
+
yield "-#{c}: argument required."
|
880
|
+
break
|
881
|
+
elsif @opts_mayparam.include?(c)
|
882
|
+
options[c] = i+1 < argstr.length ? argstr[(i+1)..-1] : true
|
883
|
+
break
|
884
|
+
#; [!tmx6o] error when option is unknown.
|
885
|
+
else
|
886
|
+
yield "-#{c}: unknown option."
|
887
|
+
i += 1
|
888
|
+
end
|
889
|
+
end
|
890
|
+
else
|
891
|
+
raise "** internall error"
|
892
|
+
end
|
893
|
+
end
|
894
|
+
return options, keyvals
|
895
|
+
end
|
896
|
+
|
897
|
+
def self.parse_options(argv=ARGV, &b)
|
898
|
+
parser = self.new("hvqcCS", "wnixosF", "I")
|
899
|
+
options, keyvals = parser.parse(argv, &b)
|
900
|
+
#; [!v19y5] converts option argument into integer if necessary.
|
901
|
+
"wnixI".each_char do |c|
|
902
|
+
next if !options.key?(c)
|
903
|
+
next if options[c] == true
|
904
|
+
#; [!frfz2] yields error message when argument of '-n/i/x/I' is not an integer.
|
905
|
+
options[c] =~ /\A\d+\z/ or
|
906
|
+
yield "-#{c}#{c == 'I' ? '' : ' '}#{options[c]}: integer expected."
|
907
|
+
options[c] = options[c].to_i
|
908
|
+
end
|
909
|
+
#; [!nz15w] convers '-s' option value into number (integer or float).
|
910
|
+
"s".each_char do |c|
|
911
|
+
next unless options.key?(c)
|
912
|
+
case options[c]
|
913
|
+
when /\A\d+\z/ ; options[c] = options[c].to_i
|
914
|
+
when /\A\d+\.\d+\z/ ; options[c] = options[c].to_f
|
915
|
+
else
|
916
|
+
#; [!3x1m7] yields error message when argument of '-s' is not a number.
|
917
|
+
yield "-#{c} #{options[c]}: number expected."
|
918
|
+
end
|
919
|
+
end
|
920
|
+
#
|
921
|
+
if options['F']
|
922
|
+
#; [!emavm] yields error message when argumetn of '-F' option is invalid.
|
923
|
+
if options['F'] !~ /^(\w+)(=|!=)[^=]/
|
924
|
+
yield "-F #{options['F']}: invalid filter (expected operator is '=' or '!=')."
|
925
|
+
elsif ! ($1 == 'task' || $1 == 'tag')
|
926
|
+
yield "-F #{options['F']}: expected 'task=...' or 'tag=...'."
|
927
|
+
end
|
928
|
+
end
|
929
|
+
return options, keyvals
|
930
|
+
end
|
931
|
+
|
932
|
+
def self.help_message(command=nil)
|
933
|
+
#; [!jnm2w] returns help message.
|
934
|
+
command ||= File.basename($0)
|
935
|
+
return <<"END"
|
936
|
+
Usage: #{command} [<options>]
|
937
|
+
-h, --help : help message
|
938
|
+
-v : print Benchmarker version
|
939
|
+
-w <N> : width of task name (default: 30)
|
940
|
+
-n <N> : loop N times in each benchmark (default: 1)
|
941
|
+
-i <N> : iterates all benchmark tasks N times (default: 1)
|
942
|
+
-x <N> : ignore worst N results and best N results (default: 0)
|
943
|
+
-I[<N>] : print inverse number (= N/sec) (default: same as '-n')
|
944
|
+
-o <file> : output file in JSON format
|
945
|
+
-q : quiet a little (suppress output of each iteration)
|
946
|
+
-c : enable colorized output
|
947
|
+
-C : disable colorized output
|
948
|
+
-s <N> : sleep N seconds after each benchmark task
|
949
|
+
-S : print sample code
|
950
|
+
-F task=<...> : filter benchmark task by name (operator: '=' or '!=')
|
951
|
+
-F tag=<...> : filter benchmark task by tag (operator: '=' or '!=')
|
952
|
+
--<key>[=<val>]: define global variable `$opt_<key> = "<val>"`
|
953
|
+
END
|
954
|
+
end
|
955
|
+
|
956
|
+
end
|
957
|
+
|
958
|
+
|
959
|
+
def self.parse_cmdopts(argv=ARGV)
|
960
|
+
#; [!348ip] parses command-line options.
|
961
|
+
#; [!snqxo] exits with status code 1 if error in command option.
|
962
|
+
options, keyvals = OptionParser.parse_options(argv) do |errmsg|
|
963
|
+
$stderr.puts errmsg
|
964
|
+
exit 1
|
965
|
+
end
|
966
|
+
#; [!p3b93] prints help message if '-h' or '--help' option specified.
|
967
|
+
if options['h'] || keyvals['help']
|
968
|
+
puts OptionParser.help_message()
|
969
|
+
exit 0
|
970
|
+
end
|
971
|
+
#; [!iaryj] prints version number if '-v' option specified.
|
972
|
+
if options['v']
|
973
|
+
puts VERSION
|
974
|
+
exit 0
|
975
|
+
end
|
976
|
+
#; [!nrxsb] prints sample code if '-S' option specified.
|
977
|
+
if options['S']
|
978
|
+
puts Misc.sample_code()
|
979
|
+
exit 0
|
980
|
+
end
|
981
|
+
#; [!s7y6x] keeps command-line options in order to overwirte existing options.
|
982
|
+
#; [!nexi8] option '-w' specifies task name width.
|
983
|
+
#; [!raki9] option '-n' specifies count of loop.
|
984
|
+
#; [!mt7lw] option '-i' specifies number of iteration.
|
985
|
+
#; [!7f2k3] option '-x' specifies number of best/worst tasks removed.
|
986
|
+
#; [!r0439] option '-I' specifies inverse switch.
|
987
|
+
#; [!4c73x] option '-o' specifies outout JSON file.
|
988
|
+
#; [!02ml5] option '-q' specifies quiet mode.
|
989
|
+
#; [!e5hv0] option '-c' specifies colorize enabled.
|
990
|
+
#; [!eb5ck] option '-C' specifies colorize disabled.
|
991
|
+
#; [!6nxi8] option '-s' specifies sleep time.
|
992
|
+
#; [!muica] option '-F' specifies filter.
|
993
|
+
OPTIONS[:width] = options['w'] if options['w']
|
994
|
+
OPTIONS[:loop] = options['n'] if options['n']
|
995
|
+
OPTIONS[:iter] = options['i'] if options['i']
|
996
|
+
OPTIONS[:extra] = options['x'] if options['x']
|
997
|
+
OPTIONS[:inverse] = options['I'] if options['I']
|
998
|
+
OPTIONS[:outfile] = options['o'] if options['o']
|
999
|
+
OPTIONS[:quiet] = options['q'] if options['q']
|
1000
|
+
OPTIONS[:colorize] = true if options['c']
|
1001
|
+
OPTIONS[:colorize] = false if options['C']
|
1002
|
+
OPTIONS[:sleep] = options['s'] if options['s']
|
1003
|
+
OPTIONS[:filter] = options['F'] if options['F']
|
1004
|
+
#; [!3khc4] sets global variables if long option specified.
|
1005
|
+
keyvals.each {|k, v| eval "$opt_#{k} = #{v.inspect}" }
|
1006
|
+
#
|
1007
|
+
return options, keyvals # for testing
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
unless defined?(::BENCHMARKER_IGNORE_CMDOPTS) && ::BENCHMARKER_IGNORE_CMDOPTS
|
1011
|
+
self.parse_cmdopts(ARGV)
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
|
1018
|
+
|
1019
|
+
## for compatibility with 'benchmark.rb' (standard library)
|
1020
|
+
module Benchmark
|
1021
|
+
|
1022
|
+
def self.__new_bm(width, &block) # :nodoc:
|
1023
|
+
bm = Benchmarker.new(width: width)
|
1024
|
+
scope = Benchmarker::Scope.new(bm)
|
1025
|
+
scope.instance_exec(scope, &block)
|
1026
|
+
return bm
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
def self.bm(width=nil, &block)
|
1030
|
+
#; [!2nf07] defines and runs benchmark.
|
1031
|
+
__new_bm(width, &block).run()
|
1032
|
+
nil
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def self.bmbm(width=nil, &block)
|
1036
|
+
#; [!ezbb8] defines and runs benchmark twice, reports only 2nd result.
|
1037
|
+
__new_bm(width, &block).run(warmup: true)
|
1038
|
+
nil
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
end unless defined?(::Benchmark)
|