Skip to content

Commit dffa56c

Browse files
committed
More Async improvements.
1 parent 7f7bb63 commit dffa56c

File tree

2 files changed

+145
-62
lines changed

2 files changed

+145
-62
lines changed

examples/benchmark_async.rb

Lines changed: 99 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ def foo(latch = nil)
2323
end
2424

2525
class AsyncAlternateClass
26-
include Concurrent::Async
26+
include Concurrent::AsyncAlternate
2727
def foo(latch = nil)
2828
latch.count_down if latch
2929
end
3030
end
3131

3232
IPS_NUM = 100
3333
BMBM_NUM = 100_000
34+
SMALL_BMBM = 250
35+
36+
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
37+
puts "Long-lived objects"
38+
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
39+
puts ""
3440

3541
Benchmark.ips do |bm|
3642
celluloid = CelluloidClass.new
@@ -80,86 +86,140 @@ def foo(latch = nil)
8086
end
8187
end
8288

89+
puts ""
90+
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
91+
puts "Short-lived objects"
92+
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
93+
puts ""
94+
95+
Benchmark.bmbm do |bm|
96+
bm.report('celluloid') do
97+
latch = Concurrent::CountDownLatch.new(SMALL_BMBM)
98+
SMALL_BMBM.times { CelluloidClass.new.async.foo(latch) }
99+
latch.wait
100+
end
101+
102+
bm.report('async, global thread pool') do
103+
latch = Concurrent::CountDownLatch.new(SMALL_BMBM)
104+
SMALL_BMBM.times { AsyncAlternateClass.new.async.foo(latch) }
105+
latch.wait
106+
end
107+
end
108+
83109
__END__
84110

85111
===========================================================
86-
Async Benchmarks
112+
Async Benchmarks
87113
===========================================================
88114

89-
Computer:
115+
Computer:
90116

91-
* OS X Yosemite
92-
- Version 10.10.4
117+
* OS X Yosemite
118+
- Version 10.10.4
93119
* MacBook Pro
94-
- Retina, 13-inch, Early 2015
120+
- Retina, 13-inch, Early 2015
95121
* Processor 3.1 GHz Intel Core i7
96122
* Memory 16 GB 1867 MHz DDR3
97123
* Physical Volumes:
98124
- Apple SSD SM0512G
99-
- 500 GB
125+
- 500 GB
100126

101127
===========================================================
102-
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
128+
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
103129
===========================================================
104130

131+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132+
Long-lived objects
133+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134+
105135
Calculating -------------------------------------
106136
celluloid 24.000 i/100ms
107137
async, thread per object
108-
31.000 i/100ms
138+
36.000 i/100ms
109139
async, global thread pool
110-
31.000 i/100ms
140+
36.000 i/100ms
111141
-------------------------------------------------
112-
celluloid 277.834 9.4%) i/s - 1.392k
142+
celluloid 270.23810.4%) i/s - 1.344k
113143
async, thread per object
114-
316.3571.9%) i/s - 1.612k
144+
366.5293.3%) i/s - 1.836k
115145
async, global thread pool
116-
318.7072.2%) i/s - 1.612k
146+
365.2643.0%) i/s - 1.836k
117147

118148
Comparison:
119-
async, global thread pool: 318.7 i/s
120-
async, thread per object: 316.4 i/s - 1.01x slower
121-
celluloid: 277.8 i/s - 1.15x slower
149+
async, thread per object: 366.5 i/s
150+
async, global thread pool: 365.3 i/s - 1.00x slower
151+
celluloid: 270.2 i/s - 1.36x slower
152+
153+
Rehearsal -------------------------------------------------------------
154+
celluloid 4.110000 0.670000 4.780000 ( 4.784982)
155+
async, thread per object 3.050000 0.090000 3.140000 ( 3.128709)
156+
async, global thread pool 2.960000 0.020000 2.980000 ( 2.981984)
157+
--------------------------------------------------- total: 10.900000sec
158+
159+
user system total real
160+
celluloid 4.220000 0.700000 4.920000 ( 4.955064)
161+
async, thread per object 3.000000 0.060000 3.060000 ( 3.055045)
162+
async, global thread pool 3.150000 0.060000 3.210000 ( 3.240574)
163+
164+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165+
Short-lived objects
166+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
122167

123168
Rehearsal -------------------------------------------------------------
124-
celluloid 4.110000 0.650000 4.760000 ( 4.766239)
125-
async, thread per object 3.370000 0.100000 3.470000 ( 3.420537)
126-
async, global thread pool 3.460000 0.240000 3.700000 ( 3.598044)
127-
--------------------------------------------------- total: 11.930000sec
169+
celluloid 0.180000 0.050000 0.230000 ( 0.220111)
170+
async, global thread pool 0.090000 0.020000 0.110000 ( 0.111569)
171+
---------------------------------------------------- total: 0.340000sec
128172

129173
user system total real
130-
celluloid 4.000000 0.640000 4.640000 ( 4.652382)
131-
async, thread per object 3.640000 0.160000 3.800000 ( 3.751535)
132-
async, global thread pool 3.440000 0.220000 3.660000 ( 3.550602)
174+
celluloid 0.240000 0.120000 0.360000 ( 0.350697)
175+
async, global thread pool 0.010000 0.000000 0.010000 ( 0.013509)
133176

134177
===========================================================
135-
jruby 1.7.19 (1.9.3p551) 2015-01-29 20786bd on Java HotSpot(TM) 64-Bit Server VM 1.8.0_45-b14 +jit [darwin-x86_64]
178+
jruby 1.7.19 (1.9.3p551) 2015-01-29 20786bd on Java HotSpot(TM) 64-Bit Server VM 1.8.0_45-b14 +jit [darwin-x86_64]
136179
===========================================================
137180

181+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182+
Long-lived objects
183+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
184+
138185
Calculating -------------------------------------
139186
celluloid 2.000 i/100ms
140187
async, thread per object
141-
23.000 i/100ms
188+
24.000 i/100ms
142189
async, global thread pool
143-
60.000 i/100ms
190+
48.000 i/100ms
144191
-------------------------------------------------
145-
celluloid 155.48038.6%) i/s - 606.000
192+
celluloid 130.11540.7%) i/s - 492.000
146193
async, thread per object
147-
823.96918.2%) i/s - 3.404k
194+
896.25717.6%) i/s - 3.984k
148195
async, global thread pool
149-
852.72814.7%) i/s - 4.140k
196+
926.26211.0%) i/s - 4.560k
150197

151198
Comparison:
152-
async, global thread pool: 852.7 i/s
153-
async, thread per object: 824.0 i/s - 1.03x slower
154-
celluloid: 155.5 i/s - 5.48x slower
199+
async, global thread pool: 926.3 i/s
200+
async, thread per object: 896.3 i/s - 1.03x slower
201+
celluloid: 130.1 i/s - 7.12x slower
202+
203+
Rehearsal -------------------------------------------------------------
204+
celluloid 5.800000 1.590000 7.390000 ( 5.306000)
205+
async, thread per object 2.880000 0.190000 3.070000 ( 1.601000)
206+
async, global thread pool 2.150000 0.130000 2.280000 ( 1.172000)
207+
--------------------------------------------------- total: 12.740000sec
208+
209+
user system total real
210+
celluloid 5.590000 1.520000 7.110000 ( 5.391000)
211+
async, thread per object 2.480000 0.160000 2.640000 ( 1.364000)
212+
async, global thread pool 1.850000 0.130000 1.980000 ( 1.008000)
213+
214+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215+
Short-lived objects
216+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
155217

156218
Rehearsal -------------------------------------------------------------
157-
celluloid 5.640000 1.560000 7.200000 ( 5.480000)
158-
async, thread per object 2.660000 0.240000 2.900000 ( 1.670000)
159-
async, global thread pool 2.110000 0.240000 2.350000 ( 1.360000)
160-
--------------------------------------------------- total: 12.450000sec
219+
celluloid 1.530000 0.140000 1.670000 ( 0.597000)
220+
async, global thread pool 0.060000 0.000000 0.060000 ( 0.018000)
221+
---------------------------------------------------- total: 1.730000sec
161222

162223
user system total real
163-
celluloid 5.650000 1.540000 7.190000 ( 5.470000)
164-
async, thread per object 2.350000 0.230000 2.580000 ( 1.532000)
165-
async, global thread pool 1.910000 0.220000 2.130000 ( 1.272000)
224+
celluloid 1.160000 0.160000 1.320000 ( 0.431000)
225+
async, global thread pool 0.020000 0.000000 0.020000 ( 0.009000)

lib/concurrent/async_alternate.rb

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
require 'thread'
21
require 'concurrent/configuration'
32
require 'concurrent/ivar'
3+
require 'concurrent/synchronization/lockable_object'
44

55
module Concurrent
66

@@ -298,27 +298,20 @@ def new(*args, &block)
298298
# Delegates asynchronous, thread-safe method calls to the wrapped object.
299299
#
300300
# @!visibility private
301-
class AsyncDelegator
301+
class AsyncDelegator < Synchronization::LockableObject
302302

303-
# Create a new delegator object wrapping the given delegate,
304-
# protecting it with the given serializer, and executing it on the
305-
# given executor. Block if necessary.
303+
# Create a new delegator object wrapping the given delegate.
306304
#
307305
# @param [Object] delegate the object to wrap and delegate method calls to
308-
# @param [Array] job queue which guarantees serialization of method calls
309-
# @param [Mutex] mutex which synchronizes queue operations
310-
# @param [Boolean] blocking will block awaiting result when `true`
311-
def initialize(delegate, queue, mutex, blocking)
306+
def initialize(delegate)
307+
super()
312308
@delegate = delegate
313-
@queue = queue
314-
@mutex = mutex
315-
@blocking = blocking
309+
@queue = []
316310
@executor = Concurrent.global_io_executor
311+
ensure_ivar_visibility!
317312
end
318313

319-
# Delegates method calls to the wrapped object. For performance,
320-
# dynamically defines the given method on the delegator so that
321-
# all future calls to `method` will not be directed here.
314+
# Delegates method calls to the wrapped object.
322315
#
323316
# @param [Symbol] method the method being called
324317
# @param [Array] args zero or more arguments to the method
@@ -332,18 +325,21 @@ def method_missing(method, *args, &block)
332325
Async::validate_argc(@delegate, method, *args)
333326

334327
ivar = Concurrent::IVar.new
335-
@mutex.synchronize do
328+
synchronize do
336329
@queue.push [ivar, method, args, block]
337330
@executor.post { perform } if @queue.length == 1
338331
end
339332

340-
ivar.wait if @blocking
341333
ivar
342334
end
343335

336+
# Perform all enqueued tasks.
337+
#
338+
# This method must be called from within the executor. It must not be
339+
# called while already running. It will loop until the queue is empty.
344340
def perform
345341
loop do
346-
ivar, method, args, block = @mutex.synchronize { @queue.first }
342+
ivar, method, args, block = synchronize { @queue.first }
347343
break unless ivar # queue is empty
348344

349345
begin
@@ -352,12 +348,41 @@ def perform
352348
ivar.fail(error)
353349
end
354350

355-
@mutex.synchronize { @queue.shift }
351+
synchronize { @queue.shift }
356352
end
357353
end
358354
end
359355
private_constant :AsyncDelegator
360356

357+
# Delegates synchronous, thread-safe method calls to the wrapped object.
358+
#
359+
# @!visibility private
360+
class AwaitDelegator
361+
362+
# Create a new delegator object wrapping the given delegate.
363+
#
364+
# @param [AsyncDelegator] delegate the object to wrap and delegate method calls to
365+
def initialize(delegate)
366+
@delegate = delegate
367+
end
368+
369+
# Delegates method calls to the wrapped object.
370+
#
371+
# @param [Symbol] method the method being called
372+
# @param [Array] args zero or more arguments to the method
373+
#
374+
# @return [IVar] the result of the method call
375+
#
376+
# @raise [NameError] the object does not respond to `method` method
377+
# @raise [ArgumentError] the given `args` do not match the arity of `method`
378+
def method_missing(method, *args, &block)
379+
ivar = @delegate.send(method, *args, &block)
380+
ivar.wait
381+
ivar
382+
end
383+
end
384+
private_constant :AwaitDelegator
385+
361386
# Causes the chained method call to be performed asynchronously on the
362387
# object's thread. The delegated method will return a future in the
363388
# `:pending` state and the method call will have been scheduled on the
@@ -409,10 +434,8 @@ def await
409434
def init_synchronization
410435
return self if @__async_initialized__
411436
@__async_initialized__ = true
412-
queue = []
413-
mutex = Mutex.new
414-
@__await_delegator__ = AsyncDelegator.new(self, queue, mutex, true)
415-
@__async_delegator__ = AsyncDelegator.new(self, queue, mutex, false)
437+
@__async_delegator__ = AsyncDelegator.new(self)
438+
@__await_delegator__ = AwaitDelegator.new(@__async_delegator__)
416439
self
417440
end
418441
end

0 commit comments

Comments
 (0)