Skip to content

Commit ca16497

Browse files
committed
Optimized performance of Delay.
1 parent b7e55a2 commit ca16497

File tree

3 files changed

+104
-28
lines changed

3 files changed

+104
-28
lines changed

examples/lazy_and_delay.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
delay.value
1616
lazy.value
1717

18-
Benchmark.bm do |x|
19-
puts 'Benchmarking Delay...'
20-
x.report { n.times{ delay.value } }
21-
puts 'Benchmarking Lazy...'
22-
x.report { n.times{ lazy.value } }
18+
Benchmark.bmbm do |x|
19+
x.report('Delay#value') { n.times{ delay.value } }
20+
x.report('Delay#value!') { n.times{ delay.value! } }
21+
x.report('LazyReference#value') { n.times{ lazy.value } }
2322
end

lib/concurrent/delay.rb

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
module Concurrent
88

99
# Lazy evaluation of a block yielding an immutable result. Useful for
10-
# expensive operations that may never be needed. `Delay` is a more
11-
# complex and feature-rich version of `LazyReference`. It is non-blocking,
10+
# expensive operations that may never be needed. It may be non-blocking,
1211
# supports the `Obligation` interface, and accepts the injection of
1312
# custom executor upon which to execute the block. Processing of
1413
# block will be deferred until the first time `#value` is called.
@@ -29,13 +28,26 @@ module Concurrent
2928
# `Delay` includes the `Concurrent::Dereferenceable` mixin to support thread
3029
# safety of the reference returned by `#value`.
3130
#
31+
# @!macro [attach] delay_note_regarding_blocking
32+
# @note The default behavior of `Delay` is to block indefinitely when
33+
# calling either `value` or `wait`, executing the delayed operation on
34+
# the current thread. This makes the `timeout` value completely
35+
# irrelevant. To enable non-blocking behavior, use the `executor`
36+
# constructor option. This will cause the delayed operation to be
37+
# execute on the given executor, allowing the call to timeout.
38+
#
3239
# Because of its simplicity `LazyReference` is much faster than `Delay`:
3340
#
34-
# user system total real
35-
# Benchmarking Delay...
36-
# 0.730000 0.000000 0.730000 ( 0.738434)
37-
# Benchmarking LazyReference...
38-
# 0.040000 0.000000 0.040000 ( 0.042322)
41+
# Rehearsal -------------------------------------------------------
42+
# Delay#value 0.210000 0.000000 0.210000 ( 0.208207)
43+
# Delay#value! 0.240000 0.000000 0.240000 ( 0.247136)
44+
# LazyReference#value 0.160000 0.000000 0.160000 ( 0.158399)
45+
# ---------------------------------------------- total: 0.610000sec
46+
#
47+
# user system total real
48+
# Delay#value 0.200000 0.000000 0.200000 ( 0.203602)
49+
# Delay#value! 0.250000 0.000000 0.250000 ( 0.252535)
50+
# LazyReference#value 0.150000 0.000000 0.150000 ( 0.154053)
3951
#
4052
# @see Concurrent::Dereferenceable
4153
# @see Concurrent::LazyReference
@@ -54,11 +66,67 @@ def initialize(opts = {}, &block)
5466
raise ArgumentError.new('no block given') unless block_given?
5567

5668
init_obligation
57-
@state = :pending
58-
@task = block
69+
70+
@state = :pending
71+
@task = block
72+
@computing = false
73+
5974
set_deref_options(opts)
60-
@task_executor = get_executor_from(opts) || Concurrent::GLOBAL_IMMEDIATE_EXECUTOR
61-
@computing = false
75+
76+
@task_executor = get_executor_from(opts)
77+
end
78+
79+
# Return the value this object represents after applying the options
80+
# specified by the `#set_deref_options` method. If the delayed operation
81+
# raised an exception this method will return nil. The execption object
82+
# can be accessed via the `#reason` method.
83+
#
84+
# @param [Numeric] timeout the maximum number of seconds to wait
85+
# @return [Object] the current value of the object
86+
#
87+
# @!macro delay_note_regarding_blocking
88+
def value(timeout = nil)
89+
if @task_executor
90+
super
91+
else
92+
# this function has been optimized for performance and
93+
# should not be modified without running new benchmarks
94+
mutex.synchronize do
95+
execute = @computing = true unless @computing
96+
if execute
97+
begin
98+
set_state(true, @task.call, nil)
99+
rescue => ex
100+
set_state(false, nil, ex)
101+
end
102+
end
103+
end
104+
if @do_nothing_on_deref
105+
@value
106+
else
107+
apply_deref_options(@value)
108+
end
109+
end
110+
end
111+
112+
# Return the value this object represents after applying the options
113+
# specified by the `#set_deref_options` method. If the delayed operation
114+
# raised an exception, this method will raise that exception (even when)
115+
# the operation has already been executed).
116+
#
117+
# @param [Numeric] timeout the maximum number of seconds to wait
118+
# @return [Object] the current value of the object
119+
# @raise [Exception] when `#rejected?` raises `#reason`
120+
#
121+
# @!macro delay_note_regarding_blocking
122+
def value!(timeout = nil)
123+
if @task_executor
124+
super
125+
else
126+
result = value
127+
raise @reason if @reason
128+
result
129+
end
62130
end
63131

64132
# Return the value this object represents after applying the options
@@ -67,10 +135,17 @@ def initialize(opts = {}, &block)
67135
# @param [Integer] timeout (nil) the maximum number of seconds to wait for
68136
# the value to be computed. When `nil` the caller will block indefinitely.
69137
#
70-
# @return [Object] the current value of the object
138+
# @return [Object] self
139+
#
140+
# @!macro delay_note_regarding_blocking
71141
def wait(timeout = nil)
72-
execute_task_once
73-
super(timeout)
142+
if @task_executor
143+
execute_task_once
144+
super(timeout)
145+
else
146+
value
147+
end
148+
self
74149
end
75150

76151
# Reconfigures the block returning the value if still `#incomplete?`
@@ -108,7 +183,7 @@ def execute_task_once # :nodoc:
108183
reason = ex
109184
end
110185
mutex.lock
111-
set_state success, result, reason
186+
set_state(success, result, reason)
112187
event.set
113188
mutex.unlock
114189
end

lib/concurrent/lazy_reference.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ module Concurrent
1010
#
1111
# Because of its simplicity `LazyReference` is much faster than `Delay`:
1212
#
13-
# Rehearsal -------------------------------------------------
14-
# Delay 0.200000 0.000000 0.200000 ( 0.201775)
15-
# LazyReference 0.150000 0.000000 0.150000 ( 0.151327)
16-
# ---------------------------------------- total: 0.350000sec
17-
#
18-
# user system total real
19-
# Delay 0.200000 0.000000 0.200000 ( 0.201151)
20-
# LazyReference 0.150000 0.000000 0.150000 ( 0.152647)
13+
# Rehearsal -------------------------------------------------------
14+
# Delay#value 0.210000 0.000000 0.210000 ( 0.208207)
15+
# Delay#value! 0.240000 0.000000 0.240000 ( 0.247136)
16+
# LazyReference#value 0.160000 0.000000 0.160000 ( 0.158399)
17+
# ---------------------------------------------- total: 0.610000sec
18+
#
19+
# user system total real
20+
# Delay#value 0.200000 0.000000 0.200000 ( 0.203602)
21+
# Delay#value! 0.250000 0.000000 0.250000 ( 0.252535)
22+
# LazyReference#value 0.150000 0.000000 0.150000 ( 0.154053)
2123
#
2224
# @see Concurrent::Delay
2325
class LazyReference

0 commit comments

Comments
 (0)