benchmark_driver 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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