benchmark_driver 0.8.6 → 0.9.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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +1 -3
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +1 -6
  5. data/README.md +51 -52
  6. data/benchmark_driver.gemspec +3 -2
  7. data/bin/console +4 -11
  8. data/examples/exec_blank.rb +2 -2
  9. data/examples/exec_blank_simple.rb +2 -3
  10. data/exe/benchmark-driver +74 -83
  11. data/lib/benchmark_driver.rb +12 -1
  12. data/lib/benchmark_driver/config.rb +36 -0
  13. data/lib/benchmark_driver/default_job.rb +12 -0
  14. data/lib/benchmark_driver/default_job_parser.rb +68 -0
  15. data/lib/benchmark_driver/job_parser.rb +42 -0
  16. data/lib/benchmark_driver/metrics.rb +17 -0
  17. data/lib/benchmark_driver/output.rb +27 -0
  18. data/lib/benchmark_driver/output/compare.rb +196 -0
  19. data/lib/benchmark_driver/output/markdown.rb +102 -0
  20. data/lib/benchmark_driver/output/simple.rb +97 -0
  21. data/lib/benchmark_driver/rbenv.rb +11 -0
  22. data/lib/benchmark_driver/ruby_interface.rb +51 -0
  23. data/lib/benchmark_driver/runner.rb +42 -0
  24. data/lib/benchmark_driver/runner/ips.rb +239 -0
  25. data/lib/benchmark_driver/runner/memory.rb +142 -0
  26. data/lib/benchmark_driver/runner/time.rb +18 -0
  27. data/lib/benchmark_driver/struct.rb +85 -0
  28. data/lib/benchmark_driver/version.rb +3 -0
  29. metadata +21 -33
  30. data/bin/bench +0 -4
  31. data/examples/call.rb +0 -12
  32. data/examples/call_blank.rb +0 -13
  33. data/examples/call_erb.rb +0 -33
  34. data/examples/call_interpolation.rb +0 -13
  35. data/examples/eval_blank.rb +0 -12
  36. data/examples/eval_blank_loop.rb +0 -13
  37. data/examples/eval_interpolation.rb +0 -15
  38. data/lib/benchmark/driver.rb +0 -101
  39. data/lib/benchmark/driver/benchmark_result.rb +0 -21
  40. data/lib/benchmark/driver/bundle_installer.rb +0 -45
  41. data/lib/benchmark/driver/bundler.rb +0 -12
  42. data/lib/benchmark/driver/configuration.rb +0 -77
  43. data/lib/benchmark/driver/duration_runner.rb +0 -24
  44. data/lib/benchmark/driver/error.rb +0 -16
  45. data/lib/benchmark/driver/repeatable_runner.rb +0 -18
  46. data/lib/benchmark/driver/ruby_dsl_parser.rb +0 -78
  47. data/lib/benchmark/driver/time.rb +0 -12
  48. data/lib/benchmark/driver/version.rb +0 -5
  49. data/lib/benchmark/driver/yaml_parser.rb +0 -55
  50. data/lib/benchmark/output.rb +0 -20
  51. data/lib/benchmark/output/ips.rb +0 -143
  52. data/lib/benchmark/output/markdown.rb +0 -73
  53. data/lib/benchmark/output/memory.rb +0 -57
  54. data/lib/benchmark/output/time.rb +0 -57
  55. data/lib/benchmark/runner.rb +0 -14
  56. data/lib/benchmark/runner/call.rb +0 -97
  57. data/lib/benchmark/runner/eval.rb +0 -147
  58. data/lib/benchmark/runner/exec.rb +0 -193
@@ -1,45 +0,0 @@
1
- require 'pathname'
2
-
3
- # TODO: Support Windows... This depends on availability of which(1)
4
- module Benchmark::Driver::BundleInstaller
5
- class << self
6
- # @param [Benchmark::Driver::Configuration::Executable] executable
7
- def bundle_install_for(executable)
8
- ruby_path = IO.popen(['which', executable.command.first], &:read).rstrip
9
- unless $?.success?
10
- abort "#{executable.command.first.dump} command was not found to execute `bundle install`"
11
- end
12
-
13
- bundler_path = Pathname.new(ruby_path).dirname.join('bundle')
14
- unless bundler_path.executable?
15
- abort "#{bundler_path.to_s.dump} was not a bundler executable, whose path was guessed from #{ruby_path.dump}"
16
- end
17
- bundle = bundler_path.to_s
18
-
19
- Bundler.with_clean_env do
20
- bundle_check(bundle, ruby: executable.name) || bundle_install(bundle)
21
- end
22
- end
23
-
24
- private
25
-
26
- # @param [String] bundle - full path to bundle(1)
27
- # @param [String] ruby - name of ruby
28
- # @return [TrueClass,FalseClass] - true if success
29
- def bundle_check(bundle, ruby:)
30
- output = IO.popen([bundle, 'check'], &:read)
31
- $?.success?.tap do |success|
32
- unless success
33
- $stderr.puts("For #{ruby}:")
34
- $stderr.print(output)
35
- end
36
- end
37
- end
38
-
39
- # @param [String] bundle - full path to bundle(1)
40
- def bundle_install(bundle)
41
- pid = Process.spawn(bundle, 'install', '-j', ENV.fetch('BUNDLE_JOBS', '4'))
42
- Process.wait(pid)
43
- end
44
- end
45
- end
@@ -1,12 +0,0 @@
1
- # This module can be transparently run even if Bundler is not installed
2
- module Benchmark::Driver::Bundler
3
- def self.with_clean_env(&block)
4
- begin
5
- require 'bundler'
6
- rescue LoadError
7
- block.call # probably bundler is not used
8
- else
9
- ::Bundler.with_clean_env { block.call }
10
- end
11
- end
12
- end
@@ -1,77 +0,0 @@
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
- Job = Struct.new(:name, :script, :prelude, :loop_count) do
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
- self[:loop_count] || guessed_count
20
- end
21
- end
22
-
23
- # @param [String] name
24
- # @param [Array<String>] command - ["ruby", "-w", ...]. First element should be path to ruby command
25
- Executable = Struct.new(:name, :command) do
26
- def self.parse(name_path)
27
- name, path = name_path.split('::', 2)
28
- Benchmark::Driver::Configuration::Executable.new(name, path ? path.split(',') : [name])
29
- end
30
-
31
- def self.parse_rbenv(spec)
32
- version, *args = spec.split(',')
33
- path = `RBENV_VERSION='#{version}' rbenv which ruby`.rstrip
34
- abort "Failed to execute 'rbenv which ruby'" unless $?.success?
35
- Benchmark::Driver::Configuration::Executable.new(version, [path, *args])
36
- end
37
- end
38
-
39
- DEFAULT_EXECUTABLES = [Executable.new(RUBY_VERSION, [RbConfig.ruby])]
40
-
41
- # @param [Symbol] type - Type of runner
42
- # @param [Array<Benchmark::Driver::Configuration::Executable>] executables
43
- # @param [Integer,nil] repeat_count - Times to repeat benchmarks. When this is not nil, benchmark_driver will use the best result.
44
- RunnerOptions = Struct.new(:type, :executables, :repeat_count, :bundler) do
45
- def initialize(*)
46
- super
47
- self.executables ||= DEFAULT_EXECUTABLES
48
- end
49
-
50
- def executables_specified?
51
- executables != DEFAULT_EXECUTABLES
52
- end
53
- end
54
-
55
- # @param [Symbol] type - Type of output
56
- # @param [TrueClass,FalseClass] compare
57
- OutputOptions = Struct.new(:type, :compare)
58
-
59
- # @param [Object] config
60
- def self.symbolize_keys!(config)
61
- case config
62
- when Hash
63
- config.keys.each do |key|
64
- config[key.to_sym] = symbolize_keys!(config.delete(key))
65
- end
66
- when Array
67
- config.map! { |c| symbolize_keys!(c) }
68
- end
69
- config
70
- end
71
-
72
- # @param [String] str
73
- def self.camelize(str)
74
- return str if str !~ /_/ && str =~ /[A-Z]+.*/
75
- str.split('_').map { |e| e.capitalize }.join
76
- end
77
- end
@@ -1,24 +0,0 @@
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
@@ -1,16 +0,0 @@
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
@@ -1,18 +0,0 @@
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
@@ -1,78 +0,0 @@
1
- require 'benchmark/driver/configuration'
2
-
3
- class Benchmark::Driver::RubyDslParser
4
- # @param [Symbol,nil] runner - If this is nil, this is automatically decided by Benchmark::Driver#runner_type_for
5
- # @param [Symbol] output
6
- def initialize(runner: nil, output: :ips)
7
- @prelude = nil
8
- @loop_count = nil
9
- @jobs = []
10
- @runner = runner
11
- @execs = nil
12
- @bundler = false
13
- @output = output
14
- @compare = false
15
- end
16
-
17
- # API to fetch configuration parsed from DSL
18
- # @return [Benchmark::Driver::Configuration]
19
- def configuration
20
- @jobs.each do |job|
21
- job.prelude = @prelude
22
- job.loop_count = @loop_count
23
- end
24
- Benchmark::Driver::Configuration.new(@jobs).tap do |c|
25
- c.runner_options = Benchmark::Driver::Configuration::RunnerOptions.new(@runner, @execs, nil, @bundler)
26
- c.output_options = Benchmark::Driver::Configuration::OutputOptions.new(@output, @compare)
27
- end
28
- end
29
-
30
- # @param [String] prelude_script - Script required for benchmark whose execution time is not measured.
31
- def prelude(prelude_script)
32
- unless prelude_script.is_a?(String)
33
- raise ArgumentError.new("prelude must be String but got #{prelude_script.inspect}")
34
- end
35
- unless @prelude.nil?
36
- raise ArgumentError.new("prelude is already set:\n#{@prelude}")
37
- end
38
-
39
- @prelude = prelude_script
40
- end
41
-
42
- # @param [Array<String>] specs
43
- def rbenv(*specs)
44
- @execs ||= []
45
- specs.each do |spec|
46
- @execs << Benchmark::Driver::Configuration::Executable.parse_rbenv(spec)
47
- end
48
- end
49
-
50
- def bundler
51
- @bundler = true
52
- end
53
-
54
- # @param [String,nil] name - Name shown on result output. This must be provided if block is given.
55
- # @param [String,nil] script - Benchmarked script in String. Only either of script or block must be provided.
56
- # @param [Proc,nil] block - Benchmarked Proc object.
57
- def report(name = nil, script = nil, &block)
58
- if !script.nil? && block_given?
59
- raise ArgumentError.new('script and block cannot be specified at the same time')
60
- elsif name.nil? && block_given?
61
- raise ArgumentError.new('name must be specified if block is given')
62
- elsif !name.nil? && !name.is_a?(String)
63
- raise ArgumentError.new("name must be String but got #{name.inspect}")
64
- elsif !script.nil? && !script.is_a?(String)
65
- raise ArgumentError.new("script must be String but got #{script.inspect}")
66
- end
67
-
68
- @jobs << Benchmark::Driver::Configuration::Job.new(name, script || block || name)
69
- end
70
-
71
- def compare!
72
- @compare = true
73
- end
74
-
75
- def loop_count(count)
76
- @loop_count = count
77
- end
78
- end
@@ -1,12 +0,0 @@
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
@@ -1,5 +0,0 @@
1
- module Benchmark
2
- module Driver
3
- VERSION = '0.8.6'
4
- end
5
- end
@@ -1,55 +0,0 @@
1
- module Benchmark::Driver::YamlParser
2
- class << self
3
- # @param [String] prelude
4
- # @param [Integer,nil] loop_count
5
- # @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
6
- # @return [Array<Benchmark::Driver::Configuration::Job>]
7
- def parse(prelude: '', loop_count: nil, benchmark:)
8
- jobs = parse_benchmark(benchmark)
9
- jobs.each do |job|
10
- job.prelude = prelude
11
- job.loop_count ||= loop_count
12
- end
13
- end
14
-
15
- private
16
-
17
- # Parse "benchmark" declarative. This may have multiple benchmarks.
18
- # @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
19
- def parse_benchmark(benchmark)
20
- case benchmark
21
- when String
22
- [parse_each_benchmark(benchmark)]
23
- when Array
24
- benchmark.map { |b| parse_each_benchmark(b) }
25
- when Hash
26
- benchmark.map do |key, value|
27
- Benchmark::Driver::Configuration::Job.new(key.to_s, value)
28
- end
29
- else
30
- raise ArgumentError.new("benchmark must be String, Array or Hash, but got: #{benchmark.inspect}")
31
- end
32
- end
33
-
34
- # Parse one benchmark specified in "benchmark" declarative.
35
- # @param [String,Hash{ Symbol => String }>] job
36
- def parse_each_benchmark(benchmark)
37
- case benchmark
38
- when String
39
- Benchmark::Driver::Configuration::Job.new(benchmark, benchmark)
40
- when Hash
41
- parse_job(benchmark)
42
- else
43
- raise ArgumentError.new("Expected String or Hash in element of benchmark, but got: #{benchmark.inspect}")
44
- end
45
- end
46
-
47
- # @param [String,nil] name
48
- # @param [String] script
49
- # TODO: support benchmark-specific prelude
50
- def parse_job(name: nil, script:)
51
- name = script if name.nil?
52
- Benchmark::Driver::Configuration::Job.new(name, script)
53
- end
54
- end
55
- end
@@ -1,20 +0,0 @@
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
- unless Benchmark::Output.const_defined?("#{class_name}::REQUIRED_FIELDS")
10
- require "benchmark/output/#{name}"
11
- end
12
- Benchmark::Output.const_get(class_name, false)
13
- end
14
- end
15
- end
16
-
17
- require 'benchmark/output/ips'
18
- require 'benchmark/output/markdown'
19
- require 'benchmark/output/memory'
20
- require 'benchmark/output/time'
@@ -1,143 +0,0 @@
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
- if @row_results.size == 1
65
- sec = @row_results[0].real
66
- iter = result.iterations
67
- if File.exist?('/proc/cpuinfo') && (clks = estimate_clock(sec, iter)) < 1_000
68
- $stdout.print(" (#{pretty_sec(sec, iter)}/i, #{clks}clocks/i)")
69
- else
70
- $stdout.print(" (#{pretty_sec(sec, iter)}/i)")
71
- end
72
- end
73
- $stdout.puts
74
- end
75
-
76
- @name_by_result[result] = executable.name
77
- end
78
-
79
- def finish
80
- if @results.size > 1 && @options.compare
81
- compare
82
- end
83
- end
84
-
85
- private
86
-
87
- def humanize(value, width = 10)
88
- scale = (Math.log10(value) / 3).to_i
89
- suffix =
90
- case scale
91
- when 1; 'k'
92
- when 2; 'M'
93
- when 3; 'G'
94
- when 4; 'T'
95
- when 5; 'Q'
96
- else # < 1000 or > 10^15, no scale or suffix
97
- scale = 0
98
- ' '
99
- end
100
- "%#{width}.3f#{suffix}" % (value.to_f / (1000 ** scale))
101
- end
102
-
103
- def pretty_sec sec, iter
104
- r = Rational(sec, iter)
105
- case
106
- when r >= 1
107
- "#{'%3.2f' % r.to_f}s"
108
- when r >= 1/1000r
109
- "#{'%3.2f' % (r * 1_000).to_f}ms"
110
- when r >= 1/1000_000r
111
- "#{'%3.2f' % (r * 1_000_000).to_f}us"
112
- else
113
- "#{'%3.2f' % (r * 1_000_000_000).to_f}ns"
114
- end
115
- end
116
-
117
- def estimate_clock sec, iter
118
- hz = File.read('/proc/cpuinfo').scan(/cpu MHz\s+:\s+([\d\.]+)/){|(f)| break hz = Rational(f.to_f) * 1_000_000}
119
- r = Rational(sec, iter)
120
- Integer(r/(1/hz))
121
- end
122
-
123
- def compare
124
- $stdout.puts("\nComparison:")
125
- results = @results.sort_by { |r| -r.ips }
126
- first = results.first
127
-
128
- results.each do |result|
129
- if result == first
130
- slower = ''
131
- else
132
- slower = '- %.2fx slower' % (first.ips / result.ips)
133
- end
134
-
135
- name = result.job.name
136
- if @executables.size > 1
137
- name = "#{name} (#{@name_by_result.fetch(result)})"
138
- end
139
- $stdout.puts("%#{NAME_LENGTH}s: %11.1f i/s #{slower}" % [name, result.ips])
140
- end
141
- $stdout.puts
142
- end
143
- end