require_relative "../../bundle/bundler/setup" # configures load paths require 'rspec/core' require 'stackprof' class << RSpec attr_writer :world end RSpec::Core::Example.class_eval do alias_method :new_with_around_and_singleton_context_hooks, :with_around_and_singleton_context_hooks alias_method :old_with_around_and_singleton_context_hooks, :with_around_example_hooks end RSpec::Core::Hooks::HookCollections.class_eval do def old_register_global_singleton_context_hooks(*) # no-op: this method didn't exist before end alias_method :new_register_global_singleton_context_hooks, :register_global_singleton_context_hooks end RSpec::Core::Configuration.class_eval do def old_configure_example(*) # no-op: this method didn't exist before end alias_method :new_configure_example, :configure_example end RSpec.configure do |c| c.output_stream = StringIO.new end require 'benchmark/ips' class BenchmarkHelpers def self.prepare_implementation(prefix) RSpec.world = RSpec::Core::World.new # clear our state RSpec::Core::Example.__send__ :alias_method, :with_around_and_singleton_context_hooks, :"#{prefix}_with_around_and_singleton_context_hooks" RSpec::Core::Hooks::HookCollections.__send__ :alias_method, :register_global_singleton_context_hooks, :"#{prefix}_register_global_singleton_context_hooks" RSpec::Core::Configuration.__send__ :alias_method, :configure_example, :"#{prefix}_configure_example" end @@runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new([])) def self.define_and_run_examples(desc, count, group_meta: {}, example_meta: {}) groups = count.times.map do |i| RSpec.describe "Group #{desc} #{i}", group_meta do 10.times { |j| example("ex #{j}", example_meta) { } } end end @@runner.run_specs(groups) end def self.profile(count, meta = { example_meta: { apply_it: true } }) [:new, :old].map do |prefix| prepare_implementation(prefix) results = StackProf.run(mode: :cpu) do define_and_run_examples("No match/#{prefix}", count, meta) end format_profile_results(results, prefix) end end def self.format_profile_results(results, prefix) File.open("tmp/#{prefix}_stack_prof_results.txt", "w") do |f| StackProf::Report.new(results).print_text(false, nil, f) end system "open tmp/#{prefix}_stack_prof_results.txt" File.open("tmp/#{prefix}_stack_prof_results.graphviz", "w") do |f| StackProf::Report.new(results).print_graphviz(nil, f) end system "dot tmp/#{prefix}_stack_prof_results.graphviz -Tpdf > tmp/#{prefix}_stack_prof_results.pdf" system "open tmp/#{prefix}_stack_prof_results.pdf" end def self.run_benchmarks Benchmark.ips do |x| implementations = { :old => "without", :new => "with" } # Historically, many of our benchmarks have initially been order-sensitive, # where whichever implementation went first got favored because defining # more groups (or whatever) would cause things to slow down. To easily # check if we're having those problems, you can pass REVERSE=1 to try # it out in the opposite order. implementations = implementations.to_a.reverse.to_h if ENV['REVERSE'] implementations.each do |prefix, description| x.report("No match -- #{description} singleton group support") do |times| prepare_implementation(prefix) define_and_run_examples("No match/#{description}", times) end end implementations.each do |prefix, description| x.report("Example match -- #{description} singleton group support") do |times| prepare_implementation(prefix) define_and_run_examples("Example match/#{description}", times, example_meta: { apply_it: true }) end end implementations.each do |prefix, description| x.report("Group match -- #{description} singleton group support") do |times| prepare_implementation(prefix) define_and_run_examples("Group match/#{description}", times, group_meta: { apply_it: true }) end end implementations.each do |prefix, description| x.report("Both match -- #{description} singleton group support") do |times| prepare_implementation(prefix) define_and_run_examples("Both match/#{description}", times, example_meta: { apply_it: true }, group_meta: { apply_it: true }) end end end end end