benchmark_driver 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +0 -4
- data/.travis.yml +10 -6
- data/Gemfile +7 -2
- data/Gemfile.lock +30 -0
- data/README.md +125 -117
- data/Rakefile +14 -7
- data/benchmark_driver.gemspec +2 -4
- data/bin/console +1 -1
- data/examples/call.rb +12 -0
- data/examples/call_blank.rb +13 -0
- data/examples/call_erb.rb +33 -0
- data/examples/call_interpolation.rb +13 -0
- data/examples/exec_blank.rb +14 -0
- data/examples/exec_interpolation.rb +15 -0
- data/examples/yaml/array_duration_time.yml +3 -0
- data/examples/yaml/array_loop.yml +3 -0
- data/examples/yaml/array_loop_memory.yml +6 -0
- data/examples/yaml/array_loop_time.yml +4 -0
- data/examples/yaml/blank_hash.yml +8 -0
- data/examples/yaml/blank_hash_array.yml +10 -0
- data/examples/yaml/blank_loop.yml +9 -0
- data/examples/yaml/blank_loop_time.yml +10 -0
- data/examples/yaml/blank_string.yml +6 -0
- data/examples/yaml/blank_string_array.yml +8 -0
- data/examples/yaml/example_multi.yml +6 -0
- data/{benchmarks → examples/yaml}/example_single.yml +0 -0
- data/exe/benchmark-driver +44 -18
- data/lib/benchmark/driver.rb +52 -257
- data/lib/benchmark/driver/benchmark_result.rb +21 -0
- data/lib/benchmark/driver/configuration.rb +65 -0
- data/lib/benchmark/driver/duration_runner.rb +24 -0
- data/lib/benchmark/driver/error.rb +16 -0
- data/lib/benchmark/driver/repeatable_runner.rb +18 -0
- data/lib/benchmark/driver/ruby_dsl_parser.rb +57 -0
- data/lib/benchmark/driver/time.rb +12 -0
- data/lib/benchmark/driver/version.rb +2 -2
- data/lib/benchmark/driver/yaml_parser.rb +103 -0
- data/lib/benchmark/output.rb +16 -0
- data/lib/benchmark/output/ips.rb +114 -0
- data/lib/benchmark/output/memory.rb +57 -0
- data/lib/benchmark/output/time.rb +57 -0
- data/lib/benchmark/runner.rb +13 -0
- data/lib/benchmark/runner/call.rb +97 -0
- data/lib/benchmark/runner/exec.rb +190 -0
- metadata +40 -10
- data/benchmarks/core/array.yml +0 -4
- data/benchmarks/example_multi.yml +0 -10
- data/benchmarks/lib/erb.yml +0 -30
@@ -0,0 +1,21 @@
|
|
1
|
+
# @param [Benchmark::Driver::Configuration::Job] job
|
2
|
+
# @param [Integer] iterations - Executed iterations of benchmark script in the job
|
3
|
+
# @param [Float] real - Real time taken by the job
|
4
|
+
# @param [Integer] max_rss - Maximum resident set size of the process during its lifetime, in Kilobytes.
|
5
|
+
class Benchmark::Driver::BenchmarkResult < Struct.new(:job, :iterations, :real, :max_rss)
|
6
|
+
alias :duration :real
|
7
|
+
|
8
|
+
def ips
|
9
|
+
iterations / real
|
10
|
+
end
|
11
|
+
|
12
|
+
def ip100ms
|
13
|
+
ips / 10
|
14
|
+
end
|
15
|
+
|
16
|
+
def iterations
|
17
|
+
# runner's warmup uses `result.ips` to calculate `job.loop_count`, and thus
|
18
|
+
# at that moment `job.loop_count` isn't available and we need to use `super`.
|
19
|
+
super || job.loop_count
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# @param [Array<Benchmark::Driver::Configuration::Job>] jobs
|
2
|
+
# @param [Benchmark::Driver::Configuration::RunnerOptions] runner_options
|
3
|
+
# @param [Benchmark::Driver::Configuration::OutputOptions] output_options
|
4
|
+
class Benchmark::Driver::Configuration < Struct.new(:jobs, :runner_options, :output_options)
|
5
|
+
# @param [String,nil] name
|
6
|
+
# @param [String,Proc] sctipt
|
7
|
+
# @param [String,nil] prelude
|
8
|
+
# @param [Integer,nil] loop_count - If this is nil, loop count is automatically estimated by warmup.
|
9
|
+
class Job < Struct.new(:name, :script, :prelude, :loop_count)
|
10
|
+
# @param [Integer,nil] guessed_count - Set by runner only when loop_count is nil. This is not configuration.
|
11
|
+
attr_accessor :guessed_count
|
12
|
+
|
13
|
+
def warmup_needed?
|
14
|
+
# This needs to check original configuration
|
15
|
+
self[:loop_count].nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def loop_count
|
19
|
+
super || guessed_count
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] name
|
24
|
+
# @param [String] path
|
25
|
+
Executable = Struct.new(:name, :path)
|
26
|
+
|
27
|
+
DEFAULT_EXECUTABLES = [Executable.new(RUBY_VERSION, RbConfig.ruby)]
|
28
|
+
|
29
|
+
# @param [Symbol] type - Type of runner
|
30
|
+
# @param [Array<Benchmark::Driver::Configuration::Executable>] executables
|
31
|
+
# @param [Integer,nil] repeat_count - Times to repeat benchmarks. When this is not nil, benchmark_driver will use the best result.
|
32
|
+
class RunnerOptions < Struct.new(:type, :executables, :repeat_count)
|
33
|
+
def initialize(*)
|
34
|
+
super
|
35
|
+
self.executables = DEFAULT_EXECUTABLES
|
36
|
+
end
|
37
|
+
|
38
|
+
def executables_specified?
|
39
|
+
executables != DEFAULT_EXECUTABLES
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Symbol] type - Type of output
|
44
|
+
# @param [TrueClass,FalseClass] compare
|
45
|
+
OutputOptions = Struct.new(:type, :compare)
|
46
|
+
|
47
|
+
# @param [Object] config
|
48
|
+
def self.symbolize_keys!(config)
|
49
|
+
case config
|
50
|
+
when Hash
|
51
|
+
config.keys.each do |key|
|
52
|
+
config[key.to_sym] = symbolize_keys!(config.delete(key))
|
53
|
+
end
|
54
|
+
when Array
|
55
|
+
config.map! { |c| symbolize_keys!(c) }
|
56
|
+
end
|
57
|
+
config
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [String] str
|
61
|
+
def self.camelize(str)
|
62
|
+
return str if str !~ /_/ && str =~ /[A-Z]+.*/
|
63
|
+
str.split('_').map { |e| e.capitalize }.join
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Benchmark::Driver::DurationRunner
|
2
|
+
# @param [Benchmark::Driver::Configuration::Job] job
|
3
|
+
def initialize(job)
|
4
|
+
@job = job
|
5
|
+
end
|
6
|
+
|
7
|
+
# @param [Integer,Float] seconds
|
8
|
+
# @param [Integer] unit_iters
|
9
|
+
# @param [Proc] runner - should take (job, unit_iters) and return duration.
|
10
|
+
# @return [Benchmark::Driver::BenchmarkResult]
|
11
|
+
def run(seconds:, unit_iters:, runner:)
|
12
|
+
real_time = 0.0
|
13
|
+
iterations = 0
|
14
|
+
unit_iters = unit_iters.to_i
|
15
|
+
|
16
|
+
benchmark_until = Benchmark::Driver::Time.now + seconds
|
17
|
+
while Benchmark::Driver::Time.now < benchmark_until
|
18
|
+
real_time += runner.call(@job, unit_iters)
|
19
|
+
iterations += unit_iters
|
20
|
+
end
|
21
|
+
|
22
|
+
Benchmark::Driver::BenchmarkResult.new(@job, iterations, real_time)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Benchmark::Driver
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class ExecutionTimeTooShort < Error
|
6
|
+
def initialize(job, iterations)
|
7
|
+
@job = job
|
8
|
+
@iterations = iterations
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"Execution time of job #{@job.name.dump} was too short in #{@iterations} executions;\n "\
|
13
|
+
'Please retry, try slower script or increase loop_count.'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Benchmark::Driver::RepeatableRunner
|
2
|
+
# @param [Benchmark::Driver::Configuration::Job] job
|
3
|
+
def initialize(job)
|
4
|
+
@job = job
|
5
|
+
end
|
6
|
+
|
7
|
+
# @param [Integer] repeat_count
|
8
|
+
# @param [Proc] runner - should take (job, unit_iters) and return duration.
|
9
|
+
# @return [Benchmark::Driver::BenchmarkResult]
|
10
|
+
def run(repeat_count:, runner:)
|
11
|
+
real_times = (repeat_count || 1).times.map do
|
12
|
+
runner.call(@job, @job.loop_count)
|
13
|
+
end
|
14
|
+
Benchmark::Driver::BenchmarkResult.new(@job).tap do |result|
|
15
|
+
result.real = real_times.select { |d| d > 0 }.min || real_times.max
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'benchmark/driver/configuration'
|
2
|
+
|
3
|
+
class Benchmark::Driver::RubyDslParser
|
4
|
+
def initialize(runner: :call, output: :ips)
|
5
|
+
@prelude = nil
|
6
|
+
@jobs = []
|
7
|
+
@runner_options = Benchmark::Driver::Configuration::RunnerOptions.new(runner)
|
8
|
+
@output_options = Benchmark::Driver::Configuration::OutputOptions.new(output)
|
9
|
+
end
|
10
|
+
|
11
|
+
# API to fetch configuration parsed from DSL
|
12
|
+
# @return [Benchmark::Driver::Configuration]
|
13
|
+
def configuration
|
14
|
+
@jobs.each do |job|
|
15
|
+
job.prelude = @prelude
|
16
|
+
end
|
17
|
+
Benchmark::Driver::Configuration.new(@jobs).tap do |c|
|
18
|
+
c.runner_options = @runner_options
|
19
|
+
c.output_options = @output_options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] prelude - Script required for benchmark whose execution time is not measured.
|
24
|
+
def prelude=(prelude)
|
25
|
+
unless prelude.is_a?(String)
|
26
|
+
raise ArgumentError.new("prelude must be String but got #{prelude.inspect}")
|
27
|
+
end
|
28
|
+
unless @prelude.nil?
|
29
|
+
raise ArgumentError.new("prelude is already set:\n#{@prelude}")
|
30
|
+
end
|
31
|
+
|
32
|
+
@prelude = prelude
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String,nil] name - Name shown on result output. This must be provided if block is given.
|
36
|
+
# @param [String,nil] script - Benchmarked script in String. Only either of script or block must be provided.
|
37
|
+
# @param [Proc,nil] block - Benchmarked Proc object.
|
38
|
+
def report(name = nil, script: nil, &block)
|
39
|
+
if script.nil? && !block_given?
|
40
|
+
raise ArgumentError.new('script or block must be provided')
|
41
|
+
elsif !script.nil? && block_given?
|
42
|
+
raise ArgumentError.new('script and block cannot be specified at the same time')
|
43
|
+
elsif name.nil? && block_given?
|
44
|
+
raise ArgumentError.new('name must be specified if block is given')
|
45
|
+
elsif !name.nil? && !name.is_a?(String)
|
46
|
+
raise ArgumentError.new("name must be String but got #{name.inspect}")
|
47
|
+
elsif !script.nil? && !script.is_a?(String)
|
48
|
+
raise ArgumentError.new("script must be String but got #{script.inspect}")
|
49
|
+
end
|
50
|
+
|
51
|
+
@jobs << Benchmark::Driver::Configuration::Job.new(name || script, script || block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def compare!
|
55
|
+
@output_options.compare = true
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Benchmark::Driver::Time
|
2
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
3
|
+
def self.now
|
4
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
5
|
+
end
|
6
|
+
else
|
7
|
+
$stderr.puts "Process::CLOCK_MONOTONIC was unavailable. Using Time."
|
8
|
+
def self.now
|
9
|
+
::Time.now
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Benchmark::Driver::YamlParser
|
2
|
+
DEFAULT_RUNNER = :exec # In YamlParser, we can't use :call.
|
3
|
+
DEFAULT_OUTPUT = :ips
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# @param [String] prelude
|
7
|
+
# @param [Integer,nil] loop_count
|
8
|
+
# @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
|
9
|
+
# @param [String,Symbol,Hash{ Symbol => Integer,TrueClass,FalseClass }] runner
|
10
|
+
# @param [String,Symbol,Hash{ Symbol => Integer,TrueClass,FalseClass }] output
|
11
|
+
# @return [Benchmark::Driver::Configuration]
|
12
|
+
def parse(prelude: '', loop_count: nil, benchmark:, runner: {}, output: {})
|
13
|
+
jobs = parse_benchmark(benchmark)
|
14
|
+
jobs.each do |job|
|
15
|
+
job.prelude = prelude
|
16
|
+
job.loop_count ||= loop_count
|
17
|
+
end
|
18
|
+
|
19
|
+
config = Benchmark::Driver::Configuration.new(jobs)
|
20
|
+
config.runner_options = parse_runner(runner)
|
21
|
+
config.output_options = parse_output(output)
|
22
|
+
config
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @param [String,Symbol,Hash{ Symbol => Integer,TrueClass,FalseClass }] runner
|
28
|
+
def parse_runner(runner)
|
29
|
+
case runner
|
30
|
+
when String, Symbol
|
31
|
+
Benchmark::Driver::Configuration::RunnerOptions.new(runner.to_sym)
|
32
|
+
when Hash
|
33
|
+
parse_runner_options(runner)
|
34
|
+
else
|
35
|
+
raise ArgumentError.new("Expected String, Symbol or Hash in runner, but got: #{runner.inspect}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_runner_options(type: DEFAULT_RUNNER, repeat_count: nil)
|
40
|
+
Benchmark::Driver::Configuration::RunnerOptions.new.tap do |r|
|
41
|
+
r.type = type.to_sym
|
42
|
+
r.repeat_count = Integer(repeat_count) if repeat_count
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String,Symbol,Hash{ Symbol => Integer,TrueClass,FalseClass }] output
|
47
|
+
def parse_output(output)
|
48
|
+
case output
|
49
|
+
when String, Symbol
|
50
|
+
Benchmark::Driver::Configuration::OutputOptions.new(output.to_sym)
|
51
|
+
when Hash
|
52
|
+
parse_output_options(output)
|
53
|
+
else
|
54
|
+
raise ArgumentError.new("Expected String, Symbol or Hash in output, but got: #{output.inspect}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_output_options(type: DEFAULT_OUTPUT, compare: false)
|
59
|
+
Benchmark::Driver::Configuration::OutputOptions.new.tap do |r|
|
60
|
+
r.type = type.to_sym
|
61
|
+
r.compare = compare
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Parse "benchmark" declarative. This may have multiple benchmarks.
|
66
|
+
# @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
|
67
|
+
def parse_benchmark(benchmark)
|
68
|
+
case benchmark
|
69
|
+
when String
|
70
|
+
[parse_each_benchmark(benchmark)]
|
71
|
+
when Array
|
72
|
+
benchmark.map { |b| parse_each_benchmark(b) }
|
73
|
+
when Hash
|
74
|
+
benchmark.map do |key, value|
|
75
|
+
Benchmark::Driver::Configuration::Job.new(key.to_s, value)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
raise ArgumentError.new("benchmark must be String, Array or Hash, but got: #{benchmark.inspect}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Parse one benchmark specified in "benchmark" declarative.
|
83
|
+
# @param [String,Hash{ Symbol => String }>] job
|
84
|
+
def parse_each_benchmark(benchmark)
|
85
|
+
case benchmark
|
86
|
+
when String
|
87
|
+
Benchmark::Driver::Configuration::Job.new(benchmark, benchmark)
|
88
|
+
when Hash
|
89
|
+
parse_job(benchmark)
|
90
|
+
else
|
91
|
+
raise ArgumentError.new("Expected String or Hash in element of benchmark, but got: #{benchmark.inspect}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [String,nil] name
|
96
|
+
# @param [String] script
|
97
|
+
# TODO: support benchmark-specific prelude
|
98
|
+
def parse_job(name: nil, script:)
|
99
|
+
name = script if name.nil?
|
100
|
+
Benchmark::Driver::Configuration::Job.new(name, script)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Benchmark::Output
|
2
|
+
class << self
|
3
|
+
# Benchmark::Output is pluggable.
|
4
|
+
# Create `Benchmark::Output::FooBar` as benchmark-output-foo_bar.gem and specify `output: foo_bar`.
|
5
|
+
#
|
6
|
+
# @param [Symbol] name
|
7
|
+
def find(name)
|
8
|
+
class_name = Benchmark::Driver::Configuration.camelize(name.to_s)
|
9
|
+
Benchmark::Output.const_get(class_name, false)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'benchmark/output/ips'
|
15
|
+
require 'benchmark/output/memory'
|
16
|
+
require 'benchmark/output/time'
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class Benchmark::Output::Ips
|
2
|
+
# This class requires runner to measure following fields in `Benchmark::Driver::BenchmarkResult` to show output.
|
3
|
+
REQUIRED_FIELDS = [:real]
|
4
|
+
|
5
|
+
NAME_LENGTH = 20
|
6
|
+
|
7
|
+
# @param [Array<Benchmark::Driver::Configuration::Job>] jobs
|
8
|
+
# @param [Array<Benchmark::Driver::Configuration::Executable>] executables
|
9
|
+
# @param [Benchmark::Driver::Configuration::OutputOptions] options
|
10
|
+
def initialize(jobs:, executables:, options:)
|
11
|
+
@jobs = jobs
|
12
|
+
@executables = executables
|
13
|
+
@options = options
|
14
|
+
@results = []
|
15
|
+
@name_by_result = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def start_warming
|
19
|
+
$stdout.puts 'Warming up --------------------------------------'
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String] name
|
23
|
+
def warming(name)
|
24
|
+
if name.length > NAME_LENGTH
|
25
|
+
$stdout.puts(name)
|
26
|
+
else
|
27
|
+
$stdout.print("%#{NAME_LENGTH}s" % name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [Benchmark::Driver::BenchmarkResult] result
|
32
|
+
def warmup_stats(result)
|
33
|
+
$stdout.puts "#{humanize(result.ip100ms)} i/100ms"
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_running
|
37
|
+
$stdout.puts 'Calculating -------------------------------------'
|
38
|
+
if @executables.size > 1
|
39
|
+
$stdout.print(' ' * NAME_LENGTH)
|
40
|
+
@executables.each do |executable|
|
41
|
+
$stdout.print(" %10s " % executable.name)
|
42
|
+
end
|
43
|
+
$stdout.puts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def running(name)
|
48
|
+
warming(name)
|
49
|
+
@row_results = []
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Benchmark::Driver::BenchmarkResult] result
|
53
|
+
def benchmark_stats(result)
|
54
|
+
executable = @executables[@row_results.size]
|
55
|
+
$stdout.print("#{humanize(result.ips, [10, executable.name.length].max)} ")
|
56
|
+
|
57
|
+
@results << result
|
58
|
+
@row_results << result
|
59
|
+
if @row_results.size == @executables.size
|
60
|
+
$stdout.print("i/s - #{humanize(result.iterations)} in")
|
61
|
+
@row_results.each do |r|
|
62
|
+
$stdout.print(" %3.6fs" % r.real)
|
63
|
+
end
|
64
|
+
$stdout.puts
|
65
|
+
end
|
66
|
+
|
67
|
+
@name_by_result[result] = executable.name
|
68
|
+
end
|
69
|
+
|
70
|
+
def finish
|
71
|
+
if @results.size > 1 && @options.compare
|
72
|
+
compare
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def humanize(value, width = 10)
|
79
|
+
scale = (Math.log10(value) / 3).to_i
|
80
|
+
suffix =
|
81
|
+
case scale
|
82
|
+
when 1; 'k'
|
83
|
+
when 2; 'M'
|
84
|
+
when 3; 'B'
|
85
|
+
when 4; 'T'
|
86
|
+
when 5; 'Q'
|
87
|
+
else # < 1000 or > 10^15, no scale or suffix
|
88
|
+
scale = 0
|
89
|
+
' '
|
90
|
+
end
|
91
|
+
"%#{width}.3f#{suffix}" % (value.to_f / (1000 ** scale))
|
92
|
+
end
|
93
|
+
|
94
|
+
def compare
|
95
|
+
$stdout.puts("\nComparison:")
|
96
|
+
results = @results.sort_by { |r| -r.ips }
|
97
|
+
first = results.first
|
98
|
+
|
99
|
+
results.each do |result|
|
100
|
+
if result == first
|
101
|
+
slower = ''
|
102
|
+
else
|
103
|
+
slower = '- %.2fx slower' % (first.ips / result.ips)
|
104
|
+
end
|
105
|
+
|
106
|
+
name = result.job.name
|
107
|
+
if @executables.size > 1
|
108
|
+
name = "#{name} (#{@name_by_result.fetch(result)})"
|
109
|
+
end
|
110
|
+
$stdout.puts("%#{NAME_LENGTH}s: %11.1f i/s #{slower}" % [name, result.ips])
|
111
|
+
end
|
112
|
+
$stdout.puts
|
113
|
+
end
|
114
|
+
end
|