benchmark_driver 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38e9cc1bf336f881a4fc545120cb2203159ac9a5daf7330e7cb0bceb1eeacd42
4
- data.tar.gz: ee7acb4616b4296164adad96e0118496a02b3d4cf2fe90441dd932d329613cb8
3
+ metadata.gz: 28707c030056cf68bac9e42f5de7acbfe8fa2700a4f23c95ee23983a76aba56b
4
+ data.tar.gz: 3734f5f19eabda233d2ad189f898c4214282c1fa7d341fafdabf34b026fe31ae
5
5
  SHA512:
6
- metadata.gz: 12eec26e9a0f747120ffe1a13aeb75bbe5aac4d9b96d4b8c5ddb01b1998f849ce2c570d5db486144ef3b622a7548a1e5adb040159993923b2dbc60de9e2053c0
7
- data.tar.gz: 790cc1ef422e7eebd2f2a83de6c1feeea6d8f313f28042c15350f8a36019dce706605fc3c1a00728c7b31023586395f99a06ceeb6cc28421cb69ad8eed1eec99
6
+ metadata.gz: 275941867a95295a4cd351009a69d84ad38c88d30b1e066a3835581c5d1b6c7c46fcc503e8980e04dcfeaac9d61e9bdbe22bdba56b6b979f3d16fa727372650e
7
+ data.tar.gz: e67add21317516a0dc3fbeadfb6cb01723260fdbac1fcfaddc06d53e25b5e309c71fdf3dabd8cc14aadff6e223fba28b5fbdb1bdfb08e3cc1a2fcc074849dd7d
data/.travis.yml CHANGED
@@ -11,4 +11,5 @@ branches:
11
11
  - master
12
12
  before_install: gem install bundler -v 1.15.4
13
13
  script:
14
+ - bundle exec rake ruby_examples
14
15
  - bundle exec rake yaml_examples
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v0.6.2
2
+
3
+ - Resurrect support of Ruby interface dropped at v0.6.0
4
+ - Invalidate wrong configuration
5
+ - Decide runner type automatically for Ruby interface
6
+
1
7
  # v0.6.1
2
8
 
3
9
  - Support markdown output
data/README.md CHANGED
@@ -18,11 +18,12 @@ NOTE: Pending ones are ~slashed~.
18
18
  ### Pluggable & Fully Featured
19
19
 
20
20
  - Flexible and real-time output format in ips, execution time, ~markdown table~, etc.
21
- - Output format is pluggable
21
+ - Runner and output are all pluggable
22
22
  - ~Integrated benchmark support using external libraries~
23
23
 
24
24
  ### Flexible Interface
25
25
 
26
+ - Ruby interface similar to benchmark stdlib, benchmark-ips
26
27
  - YAML input to easily manage structured benchmark set
27
28
  - Comparing multiple Ruby binaries, even with miniruby
28
29
 
@@ -34,6 +35,58 @@ $ gem install benchmark_driver
34
35
 
35
36
  ## Usage
36
37
 
38
+ ### Ruby Interface: Compatible Mode
39
+
40
+ This interface is compatible with `Benchmark.bm` and `Benchmark.ips`, so it's good for migration.
41
+
42
+ ```rb
43
+ require 'benchmark/driver'
44
+ require 'active_support/all'
45
+ array = []
46
+
47
+ Benchmark.driver do |x|
48
+ x.report('blank?') { array.blank? }
49
+ x.report('empty?') { array.empty? }
50
+ x.compare!
51
+ end
52
+ ```
53
+
54
+ ### Ruby Interface: Low Overhead Mode
55
+
56
+ This interface generates code to profile with low overhead and executes it.
57
+
58
+ ```rb
59
+ require 'benchmark/driver'
60
+
61
+ Benchmark.driver do |x|
62
+ x.prelude = <<~RUBY
63
+ require 'active_support/all'
64
+ array = []
65
+ RUBY
66
+
67
+ x.report('blank?', script: 'array.blank?')
68
+ x.report('empty?', script: 'array.empty?')
69
+ end
70
+ ```
71
+
72
+ or simply:
73
+
74
+ ```rb
75
+ require 'benchmark/driver'
76
+
77
+ Benchmark.driver do |x|
78
+ x.prelude = <<~RUBY
79
+ require 'active_support/all'
80
+ array = []
81
+ RUBY
82
+
83
+ x.report(script: 'array.blank?')
84
+ x.report(script: 'array.empty?')
85
+ end
86
+ ```
87
+
88
+ ### Structured YAML Input
89
+
37
90
  With `benchmark-driver` command, you can describe benchmark with YAML input.
38
91
 
39
92
  ```
@@ -45,7 +98,7 @@ Usage: benchmark-driver [options] [YAML]
45
98
  -r, --repeat-count [NUM] Try benchmark NUM times and use the fastest result
46
99
  ```
47
100
 
48
- ### Running single script
101
+ #### Running single script
49
102
 
50
103
  With following `example_single.yml`,
51
104
 
@@ -71,7 +124,7 @@ Comparison:
71
124
  erb.result (2.4.2): 109268.4 i/s - 1.13x slower
72
125
  ```
73
126
 
74
- ### Running multiple scripts
127
+ #### Running multiple scripts
75
128
 
76
129
  One YAML file can contain multiple benchmark scripts.
77
130
  With following `example_multi.yml`,
@@ -106,14 +159,15 @@ Comparison:
106
159
 
107
160
  ## TODO
108
161
  ### Runner
162
+ - [x] Call
109
163
  - [x] Exec
110
164
  - [ ] Eval
111
165
 
112
166
  ### Output
113
167
  - [x] IPS
114
168
  - [x] Time
115
- - [x] Memory
116
169
  - [ ] CPU/System/Real Time
170
+ - [ ] Memory
117
171
  - [ ] Markdown Table
118
172
 
119
173
  ## Contributing
data/Rakefile CHANGED
@@ -1,6 +1,15 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'shellwords'
3
3
 
4
+ task :ruby_examples do
5
+ Dir.glob(File.expand_path('./examples/*.rb', __dir__)).sort.each do |file|
6
+ Bundler.with_clean_env do
7
+ sh ['time', 'bundle', 'exec', 'ruby', file].shelljoin
8
+ end
9
+ puts
10
+ end
11
+ end
12
+
4
13
  task :yaml_examples do
5
14
  Dir.glob(File.expand_path('./examples/yaml/*.yml', __dir__)).sort.each do |file|
6
15
  Bundler.with_clean_env do
@@ -10,4 +19,4 @@ task :yaml_examples do
10
19
  end
11
20
  end
12
21
 
13
- task default: :yaml_examples
22
+ task default: [:ruby_examples, :yaml_examples]
data/examples/call.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'benchmark/driver'
2
+
3
+ Benchmark.driver do |x|
4
+ large_a = "Hellooooooooooooooooooooooooooooooooooooooooooooooooooo"
5
+ large_b = "Wooooooooooooooooooooooooooooooooooooooooooooooooooorld"
6
+
7
+ small_a = "Hello"
8
+ small_b = "World"
9
+
10
+ x.report('large') { "#{large_a}, #{large_b}!" }
11
+ x.report('small') { "#{small_a}, #{small_b}!" }
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'benchmark/driver'
2
+
3
+ class Array
4
+ alias_method :blank?, :empty?
5
+ end
6
+
7
+ Benchmark.driver(runner: :call) do |x|
8
+ array = []
9
+
10
+ x.report('array.empty?') { array.empty? }
11
+ x.report('array.blank?') { array.blank? }
12
+ x.compare!
13
+ end
@@ -0,0 +1,33 @@
1
+ require 'benchmark/driver'
2
+ require 'erb'
3
+ require 'erubi'
4
+ require 'erubis'
5
+
6
+ data = DATA.read
7
+
8
+ mod = Module.new
9
+ mod.instance_eval("def self.erb(title, content); #{ERB.new(data).src}; end", "(ERB)")
10
+ mod.instance_eval("def self.erubis(title, content); #{Erubi::Engine.new(data).src}; end", "(Erubi)")
11
+ mod.instance_eval("def self.erubi(title, content); #{Erubis::Eruby.new(data).src}; end", "(Erubis)")
12
+
13
+ title = "hello world!"
14
+ content = "hello world!\n" * 10
15
+
16
+ Benchmark.driver do |x|
17
+ x.report("ERB #{RUBY_VERSION}") { mod.erb(title, content) }
18
+ x.report("Erubis #{Erubis::VERSION}") { mod.erubis(title, content) }
19
+ x.report("Erubi #{Erubi::VERSION}") { mod.erubi(title, content) }
20
+ x.compare!
21
+ end
22
+
23
+ __END__
24
+
25
+ <html>
26
+ <head> <%= title %> </head>
27
+ <body>
28
+ <h1> <%= title %> </h1>
29
+ <p>
30
+ <%= content %>
31
+ </p>
32
+ </body>
33
+ </html>
@@ -0,0 +1,13 @@
1
+ require 'benchmark/driver'
2
+
3
+ Benchmark.driver do |x|
4
+ large_a = "Hellooooooooooooooooooooooooooooooooooooooooooooooooooo"
5
+ large_b = "Wooooooooooooooooooooooooooooooooooooooooooooooooooorld"
6
+
7
+ small_a = "Hello"
8
+ small_b = "World"
9
+
10
+ x.report('large') { "#{large_a}, #{large_b}!" }
11
+ x.report('small') { "#{small_a}, #{small_b}!" }
12
+ x.compare!
13
+ end
@@ -0,0 +1,14 @@
1
+ require 'benchmark/driver'
2
+
3
+ Benchmark.driver(runner: :exec) do |x|
4
+ x.prelude = <<-EOS
5
+ class Array
6
+ alias_method :blank?, :empty?
7
+ end
8
+ array = []
9
+ EOS
10
+
11
+ x.report(script: 'array.empty?')
12
+ x.report(script: 'array.blank?')
13
+ x.compare!
14
+ end
@@ -0,0 +1,15 @@
1
+ require 'benchmark/driver'
2
+
3
+ Benchmark.driver do |x|
4
+ x.prelude = <<-EOS
5
+ large_a = "Hellooooooooooooooooooooooooooooooooooooooooooooooooooo"
6
+ large_b = "Wooooooooooooooooooooooooooooooooooooooooooooooooooorld"
7
+
8
+ small_a = "Hello"
9
+ small_b = "World"
10
+ EOS
11
+
12
+ x.report('large', script: '"#{large_a}, #{large_b}!"')
13
+ x.report('small', script: '"#{small_a}, #{small_b}!"')
14
+ x.compare!
15
+ end
data/exe/benchmark-driver CHANGED
@@ -3,7 +3,6 @@ $:.unshift File.expand_path('../lib', __dir__)
3
3
 
4
4
  require 'benchmark/driver'
5
5
  require 'benchmark/driver/bundle_installer'
6
- require 'benchmark/driver/configuration'
7
6
  require 'benchmark/driver/yaml_parser'
8
7
  require 'optparse'
9
8
  require 'yaml'
@@ -1,10 +1,23 @@
1
1
  module Benchmark
2
+ # RubyDriver entrypoint.
3
+ def self.driver(*args, &block)
4
+ dsl = Driver::RubyDslParser.new(*args)
5
+ block.call(dsl)
6
+
7
+ Driver.run(dsl.configuration)
8
+ end
9
+
2
10
  module Driver
11
+ class InvalidConfig < StandardError; end
12
+
3
13
  class << self
4
- # Main function which is used by exe/benchmark-driver.
14
+ # Main function which is used by both RubyDriver and YamlDriver.
5
15
  # @param [Benchmark::Driver::Configuration] config
6
16
  def run(config)
7
17
  validate_config(config)
18
+ if config.runner_options.type.nil?
19
+ config.runner_options.type = runner_type_for(config)
20
+ end
8
21
 
9
22
  runner_class = Runner.find(config.runner_options.type)
10
23
  output_class = Output.find(config.output_options.type)
@@ -36,7 +49,19 @@ module Benchmark
36
49
  private
37
50
 
38
51
  def validate_config(config)
39
- # TODO: make sure all scripts are the same class
52
+ if config.jobs.empty?
53
+ raise InvalidConfig.new('No benchmark script is specified')
54
+ end
55
+
56
+ script_class = config.jobs.first.script.class
57
+ unless config.jobs.all? { |j| j.script.is_a?(script_class) }
58
+ raise InvalidConfig.new('Benchmark scripts include both String and Proc. Only either of them should be specified.')
59
+ end
60
+ end
61
+
62
+ def runner_type_for(config)
63
+ script_class = config.jobs.first.script.class
64
+ script_class == Proc ? :call : :exec
40
65
  end
41
66
 
42
67
  # benchmark_driver ouputs logs ASAP. This enables sync flag for it.
@@ -56,4 +81,5 @@ end
56
81
  require 'benchmark/output'
57
82
  require 'benchmark/runner'
58
83
  require 'benchmark/driver/error'
84
+ require 'benchmark/driver/ruby_dsl_parser'
59
85
  require 'benchmark/driver/version'
@@ -0,0 +1,59 @@
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
+ @jobs = []
9
+ @runner_options = Benchmark::Driver::Configuration::RunnerOptions.new(runner)
10
+ @output_options = Benchmark::Driver::Configuration::OutputOptions.new(output)
11
+ end
12
+
13
+ # API to fetch configuration parsed from DSL
14
+ # @return [Benchmark::Driver::Configuration]
15
+ def configuration
16
+ @jobs.each do |job|
17
+ job.prelude = @prelude
18
+ end
19
+ Benchmark::Driver::Configuration.new(@jobs).tap do |c|
20
+ c.runner_options = @runner_options
21
+ c.output_options = @output_options
22
+ end
23
+ end
24
+
25
+ # @param [String] prelude - Script required for benchmark whose execution time is not measured.
26
+ def prelude=(prelude)
27
+ unless prelude.is_a?(String)
28
+ raise ArgumentError.new("prelude must be String but got #{prelude.inspect}")
29
+ end
30
+ unless @prelude.nil?
31
+ raise ArgumentError.new("prelude is already set:\n#{@prelude}")
32
+ end
33
+
34
+ @prelude = prelude
35
+ end
36
+
37
+ # @param [String,nil] name - Name shown on result output. This must be provided if block is given.
38
+ # @param [String,nil] script - Benchmarked script in String. Only either of script or block must be provided.
39
+ # @param [Proc,nil] block - Benchmarked Proc object.
40
+ def report(name = nil, script: nil, &block)
41
+ if script.nil? && !block_given?
42
+ raise ArgumentError.new('script or block must be provided')
43
+ elsif !script.nil? && block_given?
44
+ raise ArgumentError.new('script and block cannot be specified at the same time')
45
+ elsif name.nil? && block_given?
46
+ raise ArgumentError.new('name must be specified if block is given')
47
+ elsif !name.nil? && !name.is_a?(String)
48
+ raise ArgumentError.new("name must be String but got #{name.inspect}")
49
+ elsif !script.nil? && !script.is_a?(String)
50
+ raise ArgumentError.new("script must be String but got #{script.inspect}")
51
+ end
52
+
53
+ @jobs << Benchmark::Driver::Configuration::Job.new(name || script, script || block)
54
+ end
55
+
56
+ def compare!
57
+ @output_options.compare = true
58
+ end
59
+ end
@@ -1,5 +1,5 @@
1
1
  module Benchmark
2
2
  module Driver
3
- VERSION = '0.6.1'
3
+ VERSION = '0.6.2'
4
4
  end
5
5
  end
@@ -2,8 +2,6 @@ module Benchmark::Runner
2
2
  # Benchmark::Runner is pluggable.
3
3
  # Create `Benchmark::Runner::FooBar` as benchmark-runner-foo_bar.gem and specify `runner: foo_bar`.
4
4
  #
5
- # Currently this has no options other than Exec, but this will have Eval for sure.
6
- #
7
5
  # @param [Symbol] name
8
6
  def self.find(name)
9
7
  class_name = Benchmark::Driver::Configuration.camelize(name.to_s)
@@ -11,4 +9,5 @@ module Benchmark::Runner
11
9
  end
12
10
  end
13
11
 
12
+ require 'benchmark/runner/call'
14
13
  require 'benchmark/runner/exec'
@@ -0,0 +1,97 @@
1
+ require 'benchmark/driver/benchmark_result'
2
+ require 'benchmark/driver/duration_runner'
3
+ require 'benchmark/driver/repeatable_runner'
4
+ require 'benchmark/driver/time'
5
+
6
+ # Run benchmark by calling #call on running ruby.
7
+ #
8
+ # Multiple Ruby binaries: x
9
+ # Memory output: x
10
+ class Benchmark::Runner::Call
11
+ # This class can provide fields in `Benchmark::Driver::BenchmarkResult` if required by output plugins.
12
+ SUPPORTED_FIELDS = [:real]
13
+
14
+ WARMUP_DURATION = 2
15
+ BENCHMARK_DURATION = 5
16
+
17
+ # @param [Benchmark::Driver::Configuration::RunnerOptions] options
18
+ # @param [Benchmark::Output::*] output - Object that responds to methods used in this class
19
+ def initialize(options, output:)
20
+ @options = options
21
+ @output = output
22
+ end
23
+
24
+ # @param [Benchmark::Driver::Configuration] config
25
+ def run(config)
26
+ validate_config(config)
27
+
28
+ if config.jobs.any?(&:warmup_needed?)
29
+ run_warmup(config.jobs)
30
+ end
31
+
32
+ @output.start_running
33
+
34
+ config.jobs.each do |job|
35
+ @output.running(job.name)
36
+
37
+ result = Benchmark::Driver::RepeatableRunner.new(job).run(
38
+ runner: method(:call_times),
39
+ repeat_count: @options.repeat_count,
40
+ )
41
+
42
+ @output.benchmark_stats(result)
43
+ end
44
+
45
+ @output.finish
46
+ end
47
+
48
+ private
49
+
50
+ def validate_config(config)
51
+ if config.runner_options.executables_specified?
52
+ raise ArgumentError.new("Benchmark::Runner::Call can't run other Ruby executables")
53
+ end
54
+
55
+ config.jobs.each do |job|
56
+ unless job.script.respond_to?(:call)
57
+ raise NotImplementedError.new(
58
+ "#{self.class.name} only accepts objects that respond to :call, but got #{job.script.inspect}"
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ # @param [Array<Benchmark::Driver::Configuration::Job>] jobs
65
+ # @return [Hash{ Benchmark::Driver::Configuration::Job => Integer }] iters_by_job
66
+ def run_warmup(jobs)
67
+ @output.start_warming
68
+
69
+ jobs.each do |job|
70
+ next if job.loop_count
71
+ @output.warming(job.name)
72
+
73
+ result = Benchmark::Driver::DurationRunner.new(job).run(
74
+ seconds: WARMUP_DURATION,
75
+ unit_iters: 1,
76
+ runner: method(:call_times),
77
+ )
78
+ job.guessed_count = (result.ips.to_f * BENCHMARK_DURATION).to_i
79
+
80
+ @output.warmup_stats(result)
81
+ end
82
+ end
83
+
84
+ def call_times(job, times)
85
+ script = job.script
86
+ i = 0
87
+
88
+ before = Benchmark::Driver::Time.now
89
+ while i < times
90
+ script.call
91
+ i += 1
92
+ end
93
+ after = Benchmark::Driver::Time.now
94
+
95
+ after.to_f - before.to_f
96
+ end
97
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchmark_driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-16 00:00:00.000000000 Z
11
+ date: 2017-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,6 +57,12 @@ files:
57
57
  - bin/bench
58
58
  - bin/console
59
59
  - bin/setup
60
+ - examples/call.rb
61
+ - examples/call_blank.rb
62
+ - examples/call_erb.rb
63
+ - examples/call_interpolation.rb
64
+ - examples/exec_blank.rb
65
+ - examples/exec_interpolation.rb
60
66
  - examples/yaml/array_duration_time.yml
61
67
  - examples/yaml/array_loop.yml
62
68
  - examples/yaml/blank_hash.yml
@@ -74,6 +80,7 @@ files:
74
80
  - lib/benchmark/driver/duration_runner.rb
75
81
  - lib/benchmark/driver/error.rb
76
82
  - lib/benchmark/driver/repeatable_runner.rb
83
+ - lib/benchmark/driver/ruby_dsl_parser.rb
77
84
  - lib/benchmark/driver/time.rb
78
85
  - lib/benchmark/driver/version.rb
79
86
  - lib/benchmark/driver/yaml_parser.rb
@@ -83,6 +90,7 @@ files:
83
90
  - lib/benchmark/output/memory.rb
84
91
  - lib/benchmark/output/time.rb
85
92
  - lib/benchmark/runner.rb
93
+ - lib/benchmark/runner/call.rb
86
94
  - lib/benchmark/runner/exec.rb
87
95
  - lib/benchmark_driver.rb
88
96
  homepage: https://github.com/k0kubun/benchmark_driver