dispatch 0.0.1pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +14 -0
- data/README.rdoc +670 -0
- data/Rakefile +2 -0
- data/dispatch.gemspec +21 -0
- data/examples/benchmarks.rb +90 -0
- data/examples/dispatch_methods.rb +277 -0
- data/examples/dispatch_methods.sh +5 -0
- data/examples/futures.rb +34 -0
- data/examples/ring_buffer.rb +95 -0
- data/examples/sleeping_barber.rb +19 -0
- data/lib/dispatch.rb +22 -0
- data/lib/dispatch/enumerable.rb +108 -0
- data/lib/dispatch/job.rb +52 -0
- data/lib/dispatch/proxy.rb +49 -0
- data/lib/dispatch/queue.rb +34 -0
- data/lib/dispatch/source.rb +83 -0
- data/lib/dispatch/version.rb +3 -0
- data/spec/enumerable_spec.rb +188 -0
- data/spec/job_spec.rb +103 -0
- data/spec/proxy_spec.rb +105 -0
- data/spec/queue_spec.rb +45 -0
- data/spec/source_spec.rb +190 -0
- data/spec/spec_helper.rb +60 -0
- metadata +99 -0
data/Rakefile
ADDED
data/dispatch.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dispatch/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dispatch"
|
7
|
+
s.version = Dispatch::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Arthur Gunn"]
|
10
|
+
s.email = ["arthur@gunn.co.nz"]
|
11
|
+
s.homepage = "https://github.com/gunn/dispatch"
|
12
|
+
s.summary = %q{Dispatch is a MacRuby wrapper around Mac OS X's Grand Central Dispatch.}
|
13
|
+
s.description = %q{Grand Central Dispatch is natively implemented as a C API and runtime engine. This gem provides a MacRuby wrapper around that API and allows Ruby to very easily take advantage of GCD to run tasks in parrallel and do calculations asynchronously with queues automatically mapping to threads as needed.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "dispatch"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/local/bin/macruby
|
2
|
+
#
|
3
|
+
# Iterate using different GCD techniques to illustrate relative performance/overhead
|
4
|
+
#
|
5
|
+
# Inspired by: https://developer.apple.com/mac/library/samplecode/Dispatch_Compared/index.html
|
6
|
+
|
7
|
+
require 'dispatch'
|
8
|
+
require 'benchmark'
|
9
|
+
|
10
|
+
$max_tasks = 256
|
11
|
+
$reps = 1024
|
12
|
+
$folds = 8
|
13
|
+
$results = nil#[]
|
14
|
+
|
15
|
+
$group = Dispatch::Group.new
|
16
|
+
$queue = Dispatch::Queue.new('org.macruby.gcd.serial')
|
17
|
+
|
18
|
+
class Benchmark
|
19
|
+
def self.repeat(count, label="", &block)
|
20
|
+
raise "count: #{count} < 1" if count < 1
|
21
|
+
block.call
|
22
|
+
t = measure {count.times &block} / count
|
23
|
+
Tms.new(*t.to_a[1..-1], label)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def work_function(i)
|
28
|
+
x = 1.0+i*i
|
29
|
+
$folds.times {|j| x = Math::tan(Math::PI/2 - Math::atan(Math::exp(2*Math::log(Math::sqrt(x))))) }
|
30
|
+
$results[i] = x if not $results.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def times(n)
|
34
|
+
n.times {|i| work_function(i)}
|
35
|
+
end
|
36
|
+
|
37
|
+
def ptimes(n)
|
38
|
+
n.p_times {|i| work_function(i)}
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply(n)
|
42
|
+
Dispatch::Queue.concurrent.apply(n) {|i| work_function(i)}
|
43
|
+
end
|
44
|
+
|
45
|
+
def concur(n)
|
46
|
+
q = Dispatch::Queue.concurrent
|
47
|
+
n.times do |i|
|
48
|
+
q.async($group) {work_function(i)}
|
49
|
+
end
|
50
|
+
$group.wait
|
51
|
+
end
|
52
|
+
|
53
|
+
def serial(n)
|
54
|
+
n.times {|i| $queue.async {work_function(i)}}
|
55
|
+
$queue.sync { }
|
56
|
+
end
|
57
|
+
|
58
|
+
def nqueue(n)
|
59
|
+
n.times do |i|
|
60
|
+
Dispatch::Queue.new("org.macruby.gcd.multi.#{i}").async($group) {work_function(i)}
|
61
|
+
end
|
62
|
+
$group.wait
|
63
|
+
end
|
64
|
+
|
65
|
+
def njobs(n)
|
66
|
+
j = Dispatch::Job.new
|
67
|
+
n.times {|i| j.add { work_function(i) }}
|
68
|
+
j.join
|
69
|
+
end
|
70
|
+
|
71
|
+
def bench(method, count=1)
|
72
|
+
proc = Proc.new { send(method.to_sym, count) }
|
73
|
+
Benchmark.repeat($reps, "%6s" % method, &proc).real*1e6/count
|
74
|
+
end
|
75
|
+
|
76
|
+
METHODS = %w(times ptimes apply concur serial nqueue njobs)
|
77
|
+
TASKS = [t = 1]
|
78
|
+
TASKS << t *= 2 while t < $max_tasks
|
79
|
+
|
80
|
+
print "GCD BENCHMARKS\tMaxTask\t#{$max_tasks}\tFolds\t#{$folds}\tReps\t#{$reps}\n"
|
81
|
+
print "T µsec\t #{TASKS.join("\t ")}"
|
82
|
+
|
83
|
+
METHODS.each do |method|
|
84
|
+
print "\n#{method}"
|
85
|
+
TASKS.each do |n|
|
86
|
+
print "\t%6.2f" % bench(method, n)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
puts
|
90
|
+
print "Results: #{$results.join("\t")}" if not $results.nil?
|
@@ -0,0 +1,277 @@
|
|
1
|
+
#!/usr/local/bin/macruby
|
2
|
+
|
3
|
+
require 'dispatch'
|
4
|
+
job = Dispatch::Job.new { Math.sqrt(10**100) }
|
5
|
+
@result = job.value
|
6
|
+
puts "value (sync): #{@result} => 1.0e+50"
|
7
|
+
|
8
|
+
job.value {|v| puts "value (async): #{v} => 1.0e+50" } # (eventually)
|
9
|
+
job.join
|
10
|
+
puts "join done (sync)"
|
11
|
+
|
12
|
+
job.join { puts "join done (async)" }
|
13
|
+
job.add { Math.sqrt(2**64) }
|
14
|
+
job.value {|b| puts "value (async): #{b} => 4294967296.0" }
|
15
|
+
@values = job.values
|
16
|
+
puts "values: #{@values.inspect} => [1.0E50]"
|
17
|
+
job.join
|
18
|
+
puts "values: #{@values.inspect} => [1.0E50, 4294967296.0]"
|
19
|
+
job = Dispatch::Job.new {}
|
20
|
+
@hash = job.synchronize Hash.new
|
21
|
+
puts "synchronize: #{@hash.class} => Dispatch::Proxy"
|
22
|
+
|
23
|
+
puts "values: #{job.values.class} => Dispatch::Proxy"
|
24
|
+
|
25
|
+
@hash[:foo] = :bar
|
26
|
+
puts "proxy: #{@hash} => {:foo=>:bar}"
|
27
|
+
@hash.delete :foo
|
28
|
+
|
29
|
+
|
30
|
+
[64, 100].each do |n|
|
31
|
+
job.add { @hash[n] = Math.sqrt(10**n) }
|
32
|
+
end
|
33
|
+
job.join
|
34
|
+
puts "proxy: #{@hash} => {64 => 1.0E32, 100 => 1.0E50}"
|
35
|
+
|
36
|
+
@hash.inspect { |s| puts "inspect: #{s} => {64 => 1.0E32, 100 => 1.0E50}" }
|
37
|
+
delegate = @hash.__value__
|
38
|
+
puts "\n__value__: #{delegate.class} => Hash"
|
39
|
+
|
40
|
+
n = 42
|
41
|
+
job = Dispatch::Job.new { puts "n (during): #{n} => 42" }
|
42
|
+
job.join
|
43
|
+
|
44
|
+
n = 0
|
45
|
+
job = Dispatch::Job.new { n = 21 }
|
46
|
+
job.join
|
47
|
+
puts "n (after): #{n} => 0?!?"
|
48
|
+
n = 0
|
49
|
+
job = Dispatch::Job.new { n += 84 }
|
50
|
+
job.join
|
51
|
+
puts "n (+=): #{n} => 0?!?"
|
52
|
+
5.times { |i| print "#{10**i}\t" }
|
53
|
+
puts "times"
|
54
|
+
|
55
|
+
5.p_times { |i| print "#{10**i}\t" }
|
56
|
+
puts "p_times"
|
57
|
+
|
58
|
+
5.p_times(3) { |i| print "#{10**i}\t" }
|
59
|
+
puts "p_times(3)"
|
60
|
+
DAYS=%w(Mon Tue Wed Thu Fri)
|
61
|
+
DAYS.each { |day| print "#{day}\t"}
|
62
|
+
puts "each"
|
63
|
+
DAYS.p_each { |day| print "#{day}\t"}
|
64
|
+
puts "p_each"
|
65
|
+
DAYS.p_each(3) { |day| print "#{day}\t"}
|
66
|
+
puts "p_each(3)"
|
67
|
+
DAYS.each_with_index { |day, i | print "#{i}:#{day}\t"}
|
68
|
+
puts "each_with_index"
|
69
|
+
DAYS.p_each_with_index { |day, i | print "#{i}:#{day}\t"}
|
70
|
+
puts "p_each_with_index"
|
71
|
+
DAYS.p_each_with_index(3) { |day, i | print "#{i}:#{day}\t"}
|
72
|
+
puts "p_each_with_index(3)"
|
73
|
+
print (0..4).map { |i| "#{10**i}\t" }.join
|
74
|
+
puts "map"
|
75
|
+
|
76
|
+
print (0..4).p_map { |i| "#{10**i}\t" }.join
|
77
|
+
puts "p_map"
|
78
|
+
print (0..4).p_map(3) { |i| "#{10**i}\t" }.join
|
79
|
+
puts "p_map(3)"
|
80
|
+
mr = (0..4).p_mapreduce(0) { |i| 10**i }
|
81
|
+
puts "p_mapreduce: #{mr} => 11111"
|
82
|
+
mr = (0..4).p_mapreduce([], :concat) { |i| [10**i] }
|
83
|
+
puts "p_mapreduce(:concat): #{mr} => [1, 1000, 10, 100, 10000]"
|
84
|
+
|
85
|
+
mr = (0..4).p_mapreduce([], :concat, 3) { |i| [10**i] }
|
86
|
+
puts "p_mapreduce(3): #{mr} => [1000, 10000, 1, 10, 100]"
|
87
|
+
puts "find_all | p_find_all | p_find_all(3)"
|
88
|
+
puts (0..4).find_all { |i| i.odd? }.inspect
|
89
|
+
puts (0..4).p_find_all { |i| i.odd? }.inspect
|
90
|
+
puts (0..4).p_find_all(3) { |i| i.odd? }.inspect
|
91
|
+
|
92
|
+
puts "find | p_find | p_find(3)"
|
93
|
+
puts (0..4).find { |i| i == 5 }.nil? # => nil
|
94
|
+
puts (0..4).p_find { |i| i == 5 }.nil? # => nil
|
95
|
+
puts (0..4).p_find(3) { |i| i == 5 }.nil? # => nil
|
96
|
+
puts "#{(0..4).find { |i| i.odd? }} => 1"
|
97
|
+
puts "#{(0..4).p_find { |i| i.odd? }} => 1?"
|
98
|
+
puts "#{(0..4).p_find(3) { |i| i.odd? }} => 3?"
|
99
|
+
puts
|
100
|
+
puts q = Dispatch::Queue.new("org.macruby.queue.example")
|
101
|
+
q.sync { puts "queue sync" }
|
102
|
+
|
103
|
+
q.async { puts "queue async" }
|
104
|
+
|
105
|
+
puts "queue join"
|
106
|
+
q.join
|
107
|
+
puts
|
108
|
+
puts semaphore = Dispatch::Semaphore.new(0)
|
109
|
+
q.async {
|
110
|
+
puts "semaphore signal"
|
111
|
+
semaphore.signal
|
112
|
+
}
|
113
|
+
|
114
|
+
puts "semaphore wait"
|
115
|
+
semaphore.wait
|
116
|
+
|
117
|
+
|
118
|
+
puts
|
119
|
+
timer = Dispatch::Source.periodic(0.4) do |src|
|
120
|
+
puts "Dispatch::Source.periodic: #{src.data}"
|
121
|
+
end
|
122
|
+
sleep 1 # => 1 1 ...
|
123
|
+
|
124
|
+
timer.suspend!
|
125
|
+
puts "suspend!"
|
126
|
+
sleep 1
|
127
|
+
timer.resume!
|
128
|
+
puts "resume!"
|
129
|
+
sleep 1 # => 1 2 1 ...
|
130
|
+
timer.cancel!
|
131
|
+
puts "cancel!"
|
132
|
+
puts
|
133
|
+
@sum = 0
|
134
|
+
adder = Dispatch::Source.add do |s|
|
135
|
+
puts "Dispatch::Source.add: #{s.data} (#{@sum += s.data})"
|
136
|
+
semaphore.signal
|
137
|
+
end
|
138
|
+
adder << 1
|
139
|
+
semaphore.wait
|
140
|
+
puts "sum: #{@sum} => 1"
|
141
|
+
adder.suspend!
|
142
|
+
adder << 3
|
143
|
+
adder << 5
|
144
|
+
puts "sum: #{@sum} => 1"
|
145
|
+
adder.resume!
|
146
|
+
semaphore.wait
|
147
|
+
puts "sum: #{@sum} => 9"
|
148
|
+
adder.cancel!
|
149
|
+
@mask = 0
|
150
|
+
masker = Dispatch::Source.or do |s|
|
151
|
+
@mask |= s.data
|
152
|
+
puts "Dispatch::Source.or: #{s.data.to_s(2)} (#{@mask.to_s(2)})"
|
153
|
+
semaphore.signal
|
154
|
+
end
|
155
|
+
masker << 0b0001
|
156
|
+
semaphore.wait
|
157
|
+
puts "mask: #{@mask.to_s(2)} => 1"
|
158
|
+
masker.suspend!
|
159
|
+
masker << 0b0011
|
160
|
+
masker << 0b1010
|
161
|
+
puts "mask: #{@mask.to_s(2)} => 1"
|
162
|
+
masker.resume!
|
163
|
+
semaphore.wait
|
164
|
+
puts "mask: #{@mask.to_s(2)} => 1011"
|
165
|
+
masker.cancel!
|
166
|
+
puts
|
167
|
+
|
168
|
+
@event = 0
|
169
|
+
mask = Dispatch::Source::PROC_EXIT | Dispatch::Source::PROC_SIGNAL
|
170
|
+
proc_src = Dispatch::Source.process($$, mask) do |s|
|
171
|
+
@event |= s.data
|
172
|
+
puts "Dispatch::Source.process: #{s.data.to_s(2)} (#{@event.to_s(2)})"
|
173
|
+
semaphore.signal
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
semaphore2 = Dispatch::Semaphore.new(0)
|
178
|
+
@events = []
|
179
|
+
mask2 = [:exit, :fork, :exec, :signal]
|
180
|
+
proc_src2 = Dispatch::Source.process($$, mask2) do |s|
|
181
|
+
these = Dispatch::Source.data2events(s.data)
|
182
|
+
@events += these
|
183
|
+
puts "Dispatch::Source.process: #{these} (#{@events})"
|
184
|
+
semaphore2.signal
|
185
|
+
end
|
186
|
+
sig_usr1 = Signal.list["USR1"]
|
187
|
+
Signal.trap(sig_usr1, "IGNORE")
|
188
|
+
Process.kill(sig_usr1, $$)
|
189
|
+
Signal.trap(sig_usr1, "DEFAULT")
|
190
|
+
semaphore.wait
|
191
|
+
result = @event & mask
|
192
|
+
print "@event: #{result.to_s(2)} =>"
|
193
|
+
puts " #{Dispatch::Source::PROC_SIGNAL.to_s(2)} (Dispatch::Source::PROC_SIGNAL)"
|
194
|
+
proc_src.cancel!
|
195
|
+
semaphore2.wait
|
196
|
+
puts "@events: #{(result2 = @events & mask2)} => [:signal]"
|
197
|
+
proc_src2.cancel!
|
198
|
+
puts "event2num: #{Dispatch::Source.event2num(result2[0]).to_s(2)} => #{result.to_s(2)}"
|
199
|
+
puts "data2events: #{Dispatch::Source.data2events(result)} => #{result2}"
|
200
|
+
@signals = 0
|
201
|
+
sig_usr2 = Signal.list["USR2"]
|
202
|
+
signal = Dispatch::Source.signal(sig_usr2) do |s|
|
203
|
+
puts "Dispatch::Source.signal: #{s.data} (#{@signals += s.data})"
|
204
|
+
semaphore.signal
|
205
|
+
end
|
206
|
+
puts "signals: #{@signals} => 0"
|
207
|
+
signal.suspend!
|
208
|
+
Signal.trap(sig_usr2, "IGNORE")
|
209
|
+
3.times { Process.kill(sig_usr2, $$) }
|
210
|
+
Signal.trap(sig_usr2, "DEFAULT")
|
211
|
+
signal.resume!
|
212
|
+
semaphore.wait
|
213
|
+
puts "signals: #{@signals} => 3"
|
214
|
+
signal.cancel!
|
215
|
+
puts
|
216
|
+
@fevent = 0
|
217
|
+
@msg = "#{$$}-#{Time.now.to_s.gsub(' ','_')}"
|
218
|
+
puts "msg: #{@msg}"
|
219
|
+
filename = "/tmp/dispatch-#{@msg}"
|
220
|
+
puts "filename: #{filename}"
|
221
|
+
file = File.open(filename, "w")
|
222
|
+
fmask = Dispatch::Source::VNODE_DELETE | Dispatch::Source::VNODE_WRITE
|
223
|
+
file_src = Dispatch::Source.file(file.fileno, fmask, q) do |s|
|
224
|
+
@fevent |= s.data
|
225
|
+
puts "Dispatch::Source.file: #{s.data.to_s(2)} (#{@fevent.to_s(2)})"
|
226
|
+
semaphore.signal
|
227
|
+
end
|
228
|
+
file.print @msg
|
229
|
+
file.flush
|
230
|
+
file.close
|
231
|
+
semaphore.wait(0.1)
|
232
|
+
print "fevent: #{(@fevent & fmask).to_s(2)} =>"
|
233
|
+
puts " #{Dispatch::Source::VNODE_WRITE.to_s(2)} (Dispatch::Source::VNODE_WRITE)"
|
234
|
+
File.delete(filename)
|
235
|
+
semaphore.wait(0.1)
|
236
|
+
print "fevent: #{@fevent.to_s(2)} => #{fmask.to_s(2)}"
|
237
|
+
puts " (Dispatch::Source::VNODE_DELETE | Dispatch::Source::VNODE_WRITE)"
|
238
|
+
file_src.cancel!
|
239
|
+
q.join
|
240
|
+
|
241
|
+
@fevent2 = []
|
242
|
+
file = File.open(filename, "w")
|
243
|
+
fmask2 = %w(delete write)
|
244
|
+
file_src2 = Dispatch::Source.file(file, fmask2) do |s|
|
245
|
+
@fevent2 += Dispatch::Source.data2events(s.data)
|
246
|
+
puts "Dispatch::Source.file: #{Dispatch::Source.data2events(s.data)} (#{@fevent2})"
|
247
|
+
semaphore2.signal
|
248
|
+
end
|
249
|
+
file.print @msg
|
250
|
+
file.flush
|
251
|
+
semaphore2.wait(0.1)
|
252
|
+
puts "fevent2: #{@fevent2} => [:write]"
|
253
|
+
file_src2.cancel!
|
254
|
+
|
255
|
+
file = File.open(filename, "r")
|
256
|
+
@input = ""
|
257
|
+
reader = Dispatch::Source.read(file) do |s|
|
258
|
+
@input << file.read(s.data)
|
259
|
+
puts "Dispatch::Source.read: #{s.data}: #{@input}"
|
260
|
+
end
|
261
|
+
while (@input.size < @msg.size) do; end
|
262
|
+
puts "input: #{@input} => #{@msg}" # => e.g., 74323-2010-07-07_15:23:10_-0700
|
263
|
+
reader.cancel!
|
264
|
+
file = File.open(filename, "w")
|
265
|
+
@next_char = 0
|
266
|
+
writer = Dispatch::Source.write(file) do |s|
|
267
|
+
if @next_char < @msg.size then
|
268
|
+
char = @msg[@next_char]
|
269
|
+
file.write(char)
|
270
|
+
@next_char += 1
|
271
|
+
puts "Dispatch::Source.write: #{char}|#{@msg[@next_char..-1]}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
while (@next_char < @msg.size) do; end
|
275
|
+
puts "output: #{File.read(filename)} => #{@msg}" # e.g., 74323-2010-07-07_15:23:10_-0700
|
276
|
+
File.delete(filename)
|
277
|
+
|
data/examples/futures.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/local/bin/macruby
|
2
|
+
|
3
|
+
# An implementation of futures (delayed computations) on top of GCD.
|
4
|
+
# Original implementation written by Patrick Thomson.
|
5
|
+
# Improvements made by Ben Stiglitz.
|
6
|
+
|
7
|
+
include Dispatch
|
8
|
+
|
9
|
+
class Future
|
10
|
+
def initialize(&block)
|
11
|
+
# Each thread gets its own FIFO queue upon which we will dispatch
|
12
|
+
# the delayed computation passed in the &block variable.
|
13
|
+
Thread.current[:futures] ||= Queue.new("org.macruby.futures-#{Thread.current.object_id}")
|
14
|
+
# Groups are just simple layers on top of semaphores.
|
15
|
+
@group = Group.new
|
16
|
+
# Asynchronously dispatch the future to the thread-local queue.
|
17
|
+
Thread.current[:futures].async(@group) { @value = block[] }
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
# Wait fo the computation to finish. If it has already finished, then
|
22
|
+
# just return the value in question.
|
23
|
+
@group.wait
|
24
|
+
@value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
f = Future.new do
|
30
|
+
sleep 2.5
|
31
|
+
'some value'
|
32
|
+
end
|
33
|
+
|
34
|
+
p f.value
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/local/bin/macruby
|
2
|
+
# Ruby Fiber Ring Benchmark
|
3
|
+
# Adapted for GCD from: http://people.equars.com/2008/5/22/ruby-fiber-ring-benchmark
|
4
|
+
|
5
|
+
require 'benchmark'
|
6
|
+
require 'dispatch'
|
7
|
+
|
8
|
+
DEBUG = false
|
9
|
+
|
10
|
+
START = DEBUG ? 0 : 1
|
11
|
+
N_NODES = DEBUG ? 1 : 4
|
12
|
+
M_MESSAGES = DEBUG ? 0 : 3
|
13
|
+
|
14
|
+
class Node
|
15
|
+
attr_accessor :successor
|
16
|
+
attr_reader :index
|
17
|
+
def initialize(g, index, successor)
|
18
|
+
@queue = Dispatch::Queue.for(self)
|
19
|
+
@group = g
|
20
|
+
@index = index
|
21
|
+
@successor = successor
|
22
|
+
@current = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(m)
|
26
|
+
@queue.async(@group) do
|
27
|
+
case m
|
28
|
+
when 0
|
29
|
+
return
|
30
|
+
when @current
|
31
|
+
call(m-1)
|
32
|
+
else
|
33
|
+
puts "\t#{self}.call(#{m})" if DEBUG
|
34
|
+
@current = m
|
35
|
+
@successor.call(m)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"##{@index}->#{@successor.index}[#{@current}]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Ring
|
46
|
+
def initialize(n)
|
47
|
+
@group = Dispatch::Group.new
|
48
|
+
@nodes = []
|
49
|
+
setup(n)
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup(n)
|
53
|
+
last = nil
|
54
|
+
n.downto(1) do |i|
|
55
|
+
@nodes << Node.new(@group, i, last)
|
56
|
+
last = @nodes[-1]
|
57
|
+
end
|
58
|
+
@nodes[0].successor = last
|
59
|
+
end
|
60
|
+
|
61
|
+
def call(m)
|
62
|
+
@nodes[-1].call(m)
|
63
|
+
@group.wait
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
@nodes.reverse.join " | "
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def bench(n,m)
|
72
|
+
tm = Benchmark.measure {
|
73
|
+
yield
|
74
|
+
}.format("%8.6r\n").gsub!(/\(|\)/, "")
|
75
|
+
|
76
|
+
puts "#{n}, #{m}, #{tm}"
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
START.upto N_NODES do |p|
|
81
|
+
n = 10**p
|
82
|
+
ring = Ring.new n
|
83
|
+
puts "\nRing of size #{n}:"
|
84
|
+
puts "\t#{ring}" if DEBUG
|
85
|
+
START.upto(M_MESSAGES) do |q|
|
86
|
+
r = 10**q
|
87
|
+
[r, 2*r, 5*r].each do |m|
|
88
|
+
puts "#{m} message(s)" if DEBUG
|
89
|
+
bench(n,m) { ring.call m }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|