Add a benchmark-driver runner for Ractor (#4172)

* Add a benchmark-driver runner for Ractor

* Process.clock_gettime(Process:CLOCK_MONOTONIC) could be slow

in Ruby 3.0 Ractor

* Fetching Time could also be slow

* Fix a comment

* Assert overriding a private method
This commit is contained in:
Takashi Kokubun 2021-02-10 21:24:25 -08:00 коммит произвёл GitHub
Родитель 9e66c511ff
Коммит 27382eb9fc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 132 добавлений и 1 удалений

Просмотреть файл

@ -0,0 +1,119 @@
require 'erb'
# A runner to measure performance *inside* Ractor
class BenchmarkDriver::Runner::Ractor < BenchmarkDriver::Runner::Ips
# JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job"
Job = Class.new(BenchmarkDriver::DefaultJob) do
attr_accessor :ractor
end
# Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
JobParser = BenchmarkDriver::DefaultJobParser.for(klass: Job, metrics: [METRIC]).extend(Module.new{
def parse(ractor: 1, **kwargs)
super(**kwargs).each do |job|
job.ractor = ractor
end
end
})
private
unless private_instance_methods.include?(:run_benchmark)
raise "#run_benchmark is no longer defined in BenchmarkDriver::Runner::Ips"
end
# @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil
# @param [BenchmarkDriver::Context] context
# @return [BenchmarkDriver::Metrics]
def run_benchmark(job, context:)
benchmark = BenchmarkScript.new(
preludes: [context.prelude, job.prelude],
script: job.script,
teardown: job.teardown,
loop_count: job.loop_count,
)
results = job.ractor.times.map do
Tempfile.open('benchmark_driver_result')
end
duration = with_script(benchmark.render(results: results.map(&:path))) do |path|
success = execute(*context.executable.command, path, exception: false)
if success && ((value = results.map { |f| Float(f.read) }.max) > 0)
value
else
BenchmarkDriver::Result::ERROR
end
end
results.each(&:close)
value_duration(
loop_count: job.loop_count,
duration: duration,
)
end
# @param [String] prelude
# @param [String] script
# @param [String] teardown
# @param [Integer] loop_count
BenchmarkScript = ::BenchmarkDriver::Struct.new(:preludes, :script, :teardown, :loop_count) do
# @param [String] result - A file to write result
def render(results:)
prelude = preludes.reject(&:nil?).reject(&:empty?).join("\n")
ERB.new(<<-RUBY).result_with_hash(results: results)
Warning[:experimental] = false
# shareable-constant-value: experimental_everything
#{prelude}
if #{loop_count} == 1
__bmdv_empty_before = 0
__bmdv_empty_after = 0
else
__bmdv_empty_before = Time.new
#{while_loop('', loop_count, id: 0)}
__bmdv_empty_after = Time.new
end
ractors = []
<% results.each do |result| %>
ractors << Ractor.new(__bmdv_empty_after - __bmdv_empty_before) { |loop_time|
__bmdv_time = Time
__bmdv_script_before = __bmdv_time.new
#{while_loop(script, loop_count, id: 1)}
__bmdv_script_after = __bmdv_time.new
File.write(
<%= result.dump %>,
((__bmdv_script_after - __bmdv_script_before) - loop_time).inspect,
)
}
<% end %>
ractors.each(&:take)
#{teardown}
RUBY
end
private
# id is to prevent:
# can not isolate a Proc because it accesses outer variables (__bmdv_i)
def while_loop(content, times, id:)
if !times.is_a?(Integer) || times <= 0
raise ArgumentError.new("Unexpected times: #{times.inspect}")
elsif times == 1
return content
end
# TODO: execute in batch
<<-RUBY
__bmdv_i#{id} = 0
while __bmdv_i#{id} < #{times}
#{content}
__bmdv_i#{id} += 1
end
RUBY
end
end
private_constant :BenchmarkScript
end

Просмотреть файл

@ -0,0 +1,4 @@
type: lib/benchmark_driver/runner/ractor
benchmark:
ractor_const: Object
ractor: 1

Просмотреть файл

@ -0,0 +1,8 @@
type: lib/benchmark_driver/runner/ractor
prelude: |
FLOATS = [*0.0.step(1.0, 0.001)]
benchmark:
ractor_float_to_s: |
FLOATS.each {|f| f.to_s}
loop_count: 100
ractor: 2

Просмотреть файл

@ -48,7 +48,7 @@ GEM_PATH =
GEM_VENDOR =
BENCHMARK_DRIVER_GIT_URL = https://github.com/benchmark-driver/benchmark-driver
BENCHMARK_DRIVER_GIT_REF = v0.15.15
BENCHMARK_DRIVER_GIT_REF = v0.15.17
SIMPLECOV_GIT_URL = https://github.com/colszowka/simplecov.git
SIMPLECOV_GIT_REF = v0.17.0
SIMPLECOV_HTML_GIT_URL = https://github.com/colszowka/simplecov-html.git