proco 0.0.1
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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +346 -0
- data/Rakefile +26 -0
- data/benchmark/comparison.rb +103 -0
- data/benchmark/submission.rb +150 -0
- data/lib/proco.rb +112 -0
- data/lib/proco/dispatcher.rb +59 -0
- data/lib/proco/future.rb +40 -0
- data/lib/proco/logger.rb +21 -0
- data/lib/proco/mt/base.rb +65 -0
- data/lib/proco/mt/pool.rb +53 -0
- data/lib/proco/mt/threaded.rb +52 -0
- data/lib/proco/mt/worker.rb +81 -0
- data/lib/proco/queue/base.rb +58 -0
- data/lib/proco/queue/batch_queue.rb +30 -0
- data/lib/proco/queue/multi_queue.rb +30 -0
- data/lib/proco/queue/single_queue.rb +23 -0
- data/lib/proco/version.rb +3 -0
- data/proco.gemspec +24 -0
- data/test/test_mt_base.rb +40 -0
- data/test/test_mt_threaded.rb +32 -0
- data/test/test_pool.rb +20 -0
- data/test/test_proco.rb +197 -0
- data/test/test_queue.rb +105 -0
- data/test/test_worker.rb +19 -0
- metadata +144 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Junegunn Choi
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
Proco
|
|
2
|
+
=====
|
|
3
|
+
|
|
4
|
+
Proco is a lightweight asynchronous task executor service with a thread pool
|
|
5
|
+
especially designed for efficient batch processing of multiple data items.
|
|
6
|
+
|
|
7
|
+
### What Proco is
|
|
8
|
+
- Lightweight, easy-to-use building block for concurrency in Ruby
|
|
9
|
+
- High-throughput reactor for relatively simple, short-lived tasks
|
|
10
|
+
- Proco can dispatch hundreds of thousands of items per second
|
|
11
|
+
|
|
12
|
+
### What Proco is not
|
|
13
|
+
- Omnipotent "does-it-all" super gem
|
|
14
|
+
- Background task schedulers like Resque or DelayedJob
|
|
15
|
+
|
|
16
|
+
A quick example
|
|
17
|
+
---------------
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require 'proco'
|
|
21
|
+
|
|
22
|
+
proco = Proco.interval(0.1). # Runs every 0.1 second
|
|
23
|
+
threads(4). # 4 threads processing items every interval
|
|
24
|
+
batch(true).new # Enables batch processing mode
|
|
25
|
+
|
|
26
|
+
proco.start do |items|
|
|
27
|
+
# Batch-process items and return something
|
|
28
|
+
# ...
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Synchronous submit
|
|
32
|
+
result = proco.submit rand(1000)
|
|
33
|
+
|
|
34
|
+
# Asynchronous(!) submit (can block if the queue is full)
|
|
35
|
+
future = proco.submit! rand(1000)
|
|
36
|
+
|
|
37
|
+
# Wait until the batch containing the item is processed
|
|
38
|
+
# (Commit notification)
|
|
39
|
+
result = future.get
|
|
40
|
+
|
|
41
|
+
# Process remaining submissions and terminate threads
|
|
42
|
+
proco.exit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requirements
|
|
46
|
+
------------
|
|
47
|
+
|
|
48
|
+
Proco requires Ruby 1.8 or higher. Tested on MRI 1.8.7/1.9.3/2.0.0, and JRuby 1.7.3.
|
|
49
|
+
|
|
50
|
+
Installation
|
|
51
|
+
------------
|
|
52
|
+
|
|
53
|
+
gem install proco
|
|
54
|
+
|
|
55
|
+
Architecture
|
|
56
|
+
------------
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
Proco is based on the traditional [producer-consumer model](http://en.wikipedia.org/wiki/Producer-consumer_problem)
|
|
61
|
+
(hence the name *ProCo*).
|
|
62
|
+
|
|
63
|
+
- Mutliple clients simultaneously submits (*produces*) items to be processed.
|
|
64
|
+
- A client can asynchronously submit an item and optionally wait for its completion.
|
|
65
|
+
- Executor threads in the thread pool process (*consumes*) items concurrently.
|
|
66
|
+
- A submitted item is first put into a fixed sized queue.
|
|
67
|
+
- A queue has its own dedicated dispatcher thread.
|
|
68
|
+
- Each item in the queue is taken out by the dispatcher and assigned to one of the executor threads.
|
|
69
|
+
- Assignments can be done periodically at certain interval, where multiple items are assigned at once for batch processing
|
|
70
|
+
- In a highly concurrent environment, event loop of the dispatcher thread can become the bottleneck.
|
|
71
|
+
- Thus, Proco can be configured to have multiple queues and dispatcher threads
|
|
72
|
+
- However, for strict serializability (FCFS), you should just have a single queue and a single executor thread (default).
|
|
73
|
+
|
|
74
|
+
### Proco with a single queue and thread
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
proco = Proco.new
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Proco with multiple queues
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
proco = Proco.threads(5).queues(4).new
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Batch processing
|
|
91
|
+
----------------
|
|
92
|
+
|
|
93
|
+
Sometimes it really helps to process multiple items in batch instead of one at a time.
|
|
94
|
+
|
|
95
|
+
Notable examples includes:
|
|
96
|
+
- buffered disk I/O in Kernel
|
|
97
|
+
- consolidated e-mail notification
|
|
98
|
+
- database batch updates
|
|
99
|
+
- group commit of database transactions
|
|
100
|
+
|
|
101
|
+
In this scheme, we don't process a request as soon as it arrives,
|
|
102
|
+
but wait a little while hoping that we receive more requests as well,
|
|
103
|
+
so we can process them together with minimal amortized latency.
|
|
104
|
+
|
|
105
|
+
It's a pretty common pattern, that most developers will be writing similar scenarios
|
|
106
|
+
one way or another at some point. So *why don't we make the pattern reusable*?
|
|
107
|
+
|
|
108
|
+
Proco was designed with this goal in mind.
|
|
109
|
+
As described above, item assignments can be done periodically at the specified interval,
|
|
110
|
+
so that multiple items are piled up in the queue between assignments,
|
|
111
|
+
and then given to one of the executor threads at once in batch.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# Assigns items in batch every second
|
|
115
|
+
proco = Proco.interval(1).batch(true).new
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Thread pool
|
|
119
|
+
-----------
|
|
120
|
+
|
|
121
|
+
Proco implements a pool of concurrently running executor threads.
|
|
122
|
+
If you're running CRuby, multi-threading only makes sense if your task involves blocking I/O operations.
|
|
123
|
+
On JRuby or Rubinius, executor threads will run in parallel and efficiently utilize multiple cores.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
# Proco with 8 executor threads
|
|
127
|
+
proco = Proco.threads(8).new
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Proco API
|
|
131
|
+
---------
|
|
132
|
+
|
|
133
|
+
API of Proco is pretty minimal. The following flowchart summarizes the supported operations.
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
### Initialization
|
|
138
|
+
|
|
139
|
+
A Proco object can be initialized by chaining the following
|
|
140
|
+
[option initializer](https://github.com/junegunn/option_initializer) methods.
|
|
141
|
+
|
|
142
|
+
| Option | Type | Description |
|
|
143
|
+
|------------|---------|------------------------------------------------|
|
|
144
|
+
| threads | Fixnum | number of threads in the thread pool |
|
|
145
|
+
| queues | Fixnum | number of concurrent queues |
|
|
146
|
+
| queue_size | Fixnum | size of each queue |
|
|
147
|
+
| interval | Numeric | dispatcher interval for batch processing |
|
|
148
|
+
| batch | Boolean | enables batch processing mode |
|
|
149
|
+
| batch_size | Fixnum | number of maximum items to be assigned at once |
|
|
150
|
+
| logger | Logger | logger instance for debug logs |
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Initialization with method chaining
|
|
154
|
+
proco = Proco.interval(0.1).threads(8).queues(4).queue_size(100).batch(true).batch_size(10).new
|
|
155
|
+
|
|
156
|
+
# Traditional initialization with options hash is also allowed
|
|
157
|
+
proco = Proco.new(
|
|
158
|
+
interval: 0.1,
|
|
159
|
+
threads: 8,
|
|
160
|
+
queues: 4,
|
|
161
|
+
queue_size: 100,
|
|
162
|
+
batch: true,
|
|
163
|
+
batch_size: 10)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Starting
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# Regular Proco
|
|
170
|
+
proco = Proco.new
|
|
171
|
+
proco.start do |item|
|
|
172
|
+
# code for single item
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Proco in batch mode
|
|
176
|
+
proco = Proco.batch(true).new
|
|
177
|
+
proco.start do |items|
|
|
178
|
+
# code for multiple items
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Submitting items
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# Synchronous submission
|
|
186
|
+
proco.submit 100
|
|
187
|
+
|
|
188
|
+
# Asynchronous(1) submission
|
|
189
|
+
future = proco.submit! 100
|
|
190
|
+
value = future.get
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Quitting
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
# Graceful shutdown
|
|
197
|
+
proco.exit
|
|
198
|
+
|
|
199
|
+
# Immediately kills all running threads
|
|
200
|
+
proco.kill
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Benchmarks
|
|
204
|
+
----------
|
|
205
|
+
|
|
206
|
+
The purpose of the benchmarks shown here is not to present absolute
|
|
207
|
+
measurements of performance but to give you a general idea of how Proco should
|
|
208
|
+
be configured under various workloads of different characteristics.
|
|
209
|
+
|
|
210
|
+
The following benchmark results were gathered on an 8-core system with JRuby 1.7.3.
|
|
211
|
+
|
|
212
|
+
### Modeling CPU-intensive task
|
|
213
|
+
|
|
214
|
+
- The task does not involve any blocking I/O
|
|
215
|
+
- A fixed amount of CPU time is required for each item
|
|
216
|
+
- There's little benefit of batch processing as the total amount of work is just the same
|
|
217
|
+
|
|
218
|
+
#### Task definition
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
task = lambda do |item|
|
|
222
|
+
(1..10000).inject(:+)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Total amount of work is just the same
|
|
226
|
+
batch_task = lambda do |items|
|
|
227
|
+
items.each do
|
|
228
|
+
(1..10000).inject(:+)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Result
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
: Elapsed time
|
|
237
|
+
loop : *********************************************************
|
|
238
|
+
Proco.new : ************************************************************
|
|
239
|
+
Proco.threads(2).queues(1).new : *******************************
|
|
240
|
+
Proco.threads(2).queues(1).batch(true).new : ***********************************
|
|
241
|
+
Proco.threads(2).queues(4).new : *******************************
|
|
242
|
+
Proco.threads(2).queues(4).batch(true).new : ********************************
|
|
243
|
+
Proco.threads(4).queues(1).new : ****************
|
|
244
|
+
Proco.threads(4).queues(1).batch(true).new : ************************
|
|
245
|
+
Proco.threads(4).queues(4).new : ****************
|
|
246
|
+
Proco.threads(4).queues(4).batch(true).new : ********************
|
|
247
|
+
Proco.threads(8).queues(1).new : *********
|
|
248
|
+
Proco.threads(8).queues(1).batch(true).new : ******************
|
|
249
|
+
Proco.threads(8).queues(4).new : *********
|
|
250
|
+
Proco.threads(8).queues(4).batch(true).new : *************
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
##### Analysis
|
|
254
|
+
|
|
255
|
+
- Proco with default configuration is slightly slower than simple loop due to thread coordination overhead
|
|
256
|
+
- As we increase the number of threads performance increases as we utilize more CPU cores
|
|
257
|
+
- Dispatcher thread is not the bottleneck. Increasing the number of queues and their dispatcher threads doesn't do any good.
|
|
258
|
+
- Batch mode takes longer as the tasks are not uniformly distributed among threads
|
|
259
|
+
- We can set `batch_size` to limit the maximum number of items in a batch
|
|
260
|
+
|
|
261
|
+
##### Result with batch_size = 100
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
proco = Proco.batch_size(100)
|
|
265
|
+
: Elapsed time
|
|
266
|
+
loop : *********************************************************
|
|
267
|
+
proco.new : ************************************************************
|
|
268
|
+
proco.threads(2).queues(1).new : *******************************
|
|
269
|
+
proco.threads(2).queues(1).batch(true).new : *******************************
|
|
270
|
+
proco.threads(2).queues(4).new : *******************************
|
|
271
|
+
proco.threads(2).queues(4).batch(true).new : ******************************
|
|
272
|
+
proco.threads(4).queues(1).new : ****************
|
|
273
|
+
proco.threads(4).queues(1).batch(true).new : ***************
|
|
274
|
+
proco.threads(4).queues(4).new : ****************
|
|
275
|
+
proco.threads(4).queues(4).batch(true).new : ***************
|
|
276
|
+
proco.threads(8).queues(1).new : *********
|
|
277
|
+
proco.threads(8).queues(1).batch(true).new : *********
|
|
278
|
+
proco.threads(8).queues(4).new : *********
|
|
279
|
+
proco.threads(8).queues(4).batch(true).new : ********
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Modeling direct I/O on a single disk
|
|
283
|
+
|
|
284
|
+
- We're bypassing write buffer of the Kernel
|
|
285
|
+
- Time required to write data on disk is dominated by the seek time
|
|
286
|
+
- Let's assume seek time of our disk is 10ms, and data transfer rate, 50MB/sec
|
|
287
|
+
- Each request writes 50kB amount of data
|
|
288
|
+
- As we have only one disk, writes cannot occur concurrently
|
|
289
|
+
|
|
290
|
+
#### Task definition
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
# Mutex for simulating exclusive disk access
|
|
294
|
+
mtx = Mutex.new
|
|
295
|
+
|
|
296
|
+
task = lambda do |item|
|
|
297
|
+
mtx.synchronize do
|
|
298
|
+
# Seek time: 0.01 sec
|
|
299
|
+
# Transfer time: 50kB / 50MB/sec = 0.001 sec
|
|
300
|
+
sleep 0.01 + 0.001
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Let's say it makes sense to group multiple writes into a single I/O operation
|
|
305
|
+
batch_task = lambda do |items|
|
|
306
|
+
mtx.synchronize do
|
|
307
|
+
# Seek time: 0.01 sec
|
|
308
|
+
# Transfer time: n * (50kB / 50MB/sec) = n * 0.001 sec
|
|
309
|
+
sleep 0.01 + items.length * 0.001
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Result
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
loop : ***********************************************************
|
|
318
|
+
Proco.new : ***********************************************************
|
|
319
|
+
Proco.threads(2).queues(1).new : ***********************************************************
|
|
320
|
+
Proco.threads(2).queues(1).batch(true).new : ****
|
|
321
|
+
Proco.threads(2).queues(4).new : ***********************************************************
|
|
322
|
+
Proco.threads(2).queues(4).batch(true).new : *****
|
|
323
|
+
Proco.threads(4).queues(1).new : ***********************************************************
|
|
324
|
+
Proco.threads(4).queues(1).batch(true).new : ****
|
|
325
|
+
Proco.threads(4).queues(4).new : ***********************************************************
|
|
326
|
+
Proco.threads(4).queues(4).batch(true).new : *****
|
|
327
|
+
Proco.threads(8).queues(1).new : ************************************************************
|
|
328
|
+
Proco.threads(8).queues(1).batch(true).new : ****
|
|
329
|
+
Proco.threads(8).queues(4).new : ***********************************************************
|
|
330
|
+
Proco.threads(8).queues(4).batch(true).new : ****
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
##### Analysis
|
|
335
|
+
|
|
336
|
+
- The number of threads, queues or dispather threads, none of them matters
|
|
337
|
+
- Batch mode shows much better performance
|
|
338
|
+
|
|
339
|
+
Contributing
|
|
340
|
+
------------
|
|
341
|
+
|
|
342
|
+
1. Fork it
|
|
343
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
344
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
345
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
346
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
|
6
|
+
test.libs << 'lib' << 'test'
|
|
7
|
+
test.pattern = 'test/**/test_*.rb'
|
|
8
|
+
test.verbose = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
task :viz do
|
|
12
|
+
FileUtils.chdir(File.expand_path('..', __FILE__))
|
|
13
|
+
|
|
14
|
+
[ [6, 1, 5],
|
|
15
|
+
[6, 1, 1],
|
|
16
|
+
[6, 4, 5] ].each do |vars|
|
|
17
|
+
c, q, t = vars
|
|
18
|
+
ENV['C'], ENV['Q'], ENV['T'] = vars.map(&:to_s)
|
|
19
|
+
file = "viz/proco-#{vars.join '-'}.png"
|
|
20
|
+
system %[erb viz/proco.dot.erb | dot -Tpng -o #{file} && open #{file}]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
%w[producer-consumer proco-lifecycle].each do |file|
|
|
24
|
+
system %[dot -Tpng -o viz/#{file}.png viz/#{file}.dot && open viz/#{file}.png]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$VERBOSE = true
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
5
|
+
require 'proco'
|
|
6
|
+
require 'benchmark'
|
|
7
|
+
require 'parallelize'
|
|
8
|
+
require 'logger'
|
|
9
|
+
|
|
10
|
+
logger = Logger.new($stdout)
|
|
11
|
+
|
|
12
|
+
[:cpu, :directio].each do |mode|
|
|
13
|
+
if mode == :cpu
|
|
14
|
+
times = 20000
|
|
15
|
+
# CPU Intensive task
|
|
16
|
+
task = lambda do |item|
|
|
17
|
+
(1..10000).inject(:+)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
btask = lambda do |items|
|
|
21
|
+
items.each do
|
|
22
|
+
(1..10000).inject(:+)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
mtx = Mutex.new
|
|
27
|
+
|
|
28
|
+
times = 1000
|
|
29
|
+
task = lambda do |item|
|
|
30
|
+
mtx.synchronize do
|
|
31
|
+
sleep 0.01 + 0.001
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
btask = lambda do |items|
|
|
36
|
+
mtx.synchronize do
|
|
37
|
+
sleep 0.01 + 0.001 * items.length
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
result = Benchmark.bm(45) do |x|
|
|
43
|
+
x.report("loop") do
|
|
44
|
+
times.times do |i|
|
|
45
|
+
task.call i
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
x.report('Proco.new') do
|
|
50
|
+
proco = Proco.new
|
|
51
|
+
proco.start do |i|
|
|
52
|
+
task.call i
|
|
53
|
+
end
|
|
54
|
+
times.times do |i|
|
|
55
|
+
proco.submit! i
|
|
56
|
+
end
|
|
57
|
+
proco.exit
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
[2, 4, 8].each do |threads|
|
|
61
|
+
x.report("parallelize(#{threads})") do
|
|
62
|
+
parallelize(threads) do
|
|
63
|
+
(times / threads).times do |i|
|
|
64
|
+
task.call i
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
[1, 4].each do |queues|
|
|
70
|
+
x.report("Proco.threads(#{threads}).queues(#{queues}).new") do
|
|
71
|
+
proco = Proco.queues(queues).threads(threads).new
|
|
72
|
+
proco.start do |i|
|
|
73
|
+
task.call i
|
|
74
|
+
end
|
|
75
|
+
times.times do |i|
|
|
76
|
+
proco.submit! i
|
|
77
|
+
end
|
|
78
|
+
proco.exit
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
x.report("Proco.threads(#{threads}).queues(#{queues}).batch(true).new") do
|
|
82
|
+
proco = Proco.queues(queues).threads(threads).batch(true).new
|
|
83
|
+
proco.start do |items|
|
|
84
|
+
btask.call items
|
|
85
|
+
end
|
|
86
|
+
times.times do |i|
|
|
87
|
+
proco.submit! i
|
|
88
|
+
end
|
|
89
|
+
proco.exit
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
data = Hash[ result.map { |r| [r.label, r.real] } ]
|
|
96
|
+
mlen = data.keys.map(&:length).max
|
|
97
|
+
mval = data.values.max
|
|
98
|
+
width = 40
|
|
99
|
+
data.each do |k, v|
|
|
100
|
+
puts k.ljust(mlen) + ' : ' + '*' * (width * (v / mval)).to_i
|
|
101
|
+
end
|
|
102
|
+
puts
|
|
103
|
+
end
|