Skip to content

Commit

Permalink
Refactor around hook execution.
Browse files Browse the repository at this point in the history
  • Loading branch information
myronmarston committed Dec 19, 2014
1 parent b925f5f commit 48e2a3e
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 11 deletions.
50 changes: 50 additions & 0 deletions benchmarks/capture_block_vs_yield.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
require 'benchmark/ips'

def yield_control
yield
end

def capture_block_and_yield(&block)
yield
end

def capture_block_and_call(&block)
block.call
end

Benchmark.ips do |x|
x.report("yield ") do
yield_control { }
end

x.report("capture block and yield") do
capture_block_and_yield { }
end

x.report("capture block and call ") do
capture_block_and_call { }
end
end

__END__

This benchmark demonstrates that `yield` is much, much faster
than capturing `&block` and calling it. In fact, the simple act
of capturing `&block`, even if we don't later reference `&block`,
incurs most of the cost, so we should avoid capturing blocks unless
we absolutely need to.

Calculating -------------------------------------
yield
93.104k i/100ms
capture block and yield
52.682k i/100ms
capture block and call
51.115k i/100ms
-------------------------------------------------
yield
5.161M (±10.6%) i/s - 25.231M
capture block and yield
1.141M (±22.0%) i/s - 5.426M
capture block and call
1.027M (±21.8%) i/s - 4.856M
8 changes: 2 additions & 6 deletions lib/rspec/core/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,8 @@ def instance_exec(*args, &block)

private

def with_around_example_hooks(&block)
if around_example_hooks.empty?
yield
else
@example_group_class.hooks.run(:around, :example, self, Procsy.new(self, &block))
end
def with_around_example_hooks
@example_group_class.hooks.run(:around, :example, self) { yield }
rescue Exception => e
set_exception(e, "in an `around(:example)` hook")
end
Expand Down
13 changes: 8 additions & 5 deletions lib/rspec/core/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,10 @@ def run_with(example_or_group)

# @private
class AroundHookCollection < HookCollection
def run_with(example, initial_procsy)
def run_with(example)
return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`

initial_procsy = Example::Procsy.new(example) { yield }
hooks.select { |hook| hook.options_apply?(example) }.inject(initial_procsy) do |procsy, around_hook|
procsy.wrap { around_hook.execute_with(example, procsy) }
end.call
Expand Down Expand Up @@ -496,7 +499,7 @@ def register(prepend_or_append, hook, *args, &block)
#
# Runs all of the blocks stored with the hook in the context of the
# example. If no example is provided, just calls the hook directly.
def run(hook, scope, example_or_group, initial_procsy=nil)
def run(hook, scope, example_or_group)
return if RSpec.configuration.dry_run?

case [hook, scope]
Expand All @@ -505,7 +508,7 @@ def run(hook, scope, example_or_group, initial_procsy=nil)
when [:after, :context]
run_after_context_hooks_for(example_or_group)
when [:around, :example]
run_around_example_hooks_for(example_or_group, initial_procsy)
run_around_example_hooks_for(example_or_group) { yield }
when [:before, :example]
run_before_example_hooks_for(example_or_group)
when [:after, :example]
Expand Down Expand Up @@ -584,10 +587,10 @@ def run_after_example_hooks_for(example)
end).run_with(example)
end

def run_around_example_hooks_for(example, initial_procsy=nil)
def run_around_example_hooks_for(example)
AroundHookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
a.hooks[:around][:example]
end).run_with(example, initial_procsy)
end).run_with(example) { yield }
end
end
end
Expand Down

0 comments on commit 48e2a3e

Please sign in to comment.