parallel 1.10.0 → 1.24.0
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.
- checksums.yaml +5 -5
- data/lib/parallel/version.rb +2 -1
- data/lib/parallel.rb +279 -81
- metadata +9 -7
- data/lib/parallel/processor_count.rb +0 -85
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f7b4a5662ef68724c75c2d655d9bd09ad109edc02d3199a0744093544608f74b
|
|
4
|
+
data.tar.gz: 247f7f3745c42cdafeb5d403fff250e99b99a56d4dc4948050dcf20ecb60d183
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18f62ea9fefb30bda9b89f0189074da99572c5c17fb93ba28e553b0eb9f6d7a61bd70ba423004b138b05c3c86f8e04fd32fce81da40a68c0bdca39e263b8b892
|
|
7
|
+
data.tar.gz: dca61c9e3cab22de0ad064c96c55afc88177214108d6bdbfb8e9a93343f23d6ee321c83aa6c4cfa9ee33da79d01802d7b952f3dd0360f9cca26e5da78a4117fa
|
data/lib/parallel/version.rb
CHANGED
data/lib/parallel.rb
CHANGED
|
@@ -1,39 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'rbconfig'
|
|
2
3
|
require 'parallel/version'
|
|
3
|
-
require 'parallel/processor_count'
|
|
4
4
|
|
|
5
5
|
module Parallel
|
|
6
|
-
|
|
6
|
+
Stop = Object.new.freeze
|
|
7
7
|
|
|
8
8
|
class DeadWorker < StandardError
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
class Break < StandardError
|
|
12
|
+
attr_reader :value
|
|
13
|
+
|
|
14
|
+
def initialize(value = nil)
|
|
15
|
+
super()
|
|
16
|
+
@value = value
|
|
17
|
+
end
|
|
12
18
|
end
|
|
13
19
|
|
|
14
|
-
class Kill <
|
|
20
|
+
class Kill < Break
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
class UndumpableException < StandardError
|
|
24
|
+
attr_reader :backtrace
|
|
25
|
+
|
|
18
26
|
def initialize(original)
|
|
19
27
|
super "#{original.class}: #{original.message}"
|
|
20
|
-
@
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def backtrace
|
|
24
|
-
@bracktrace
|
|
28
|
+
@backtrace = original.backtrace
|
|
25
29
|
end
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
Stop = Object.new
|
|
29
|
-
|
|
30
32
|
class ExceptionWrapper
|
|
31
33
|
attr_reader :exception
|
|
34
|
+
|
|
32
35
|
def initialize(exception)
|
|
36
|
+
# Remove the bindings stack added by the better_errors gem,
|
|
37
|
+
# because it cannot be marshalled
|
|
38
|
+
if exception.instance_variable_defined? :@__better_errors_bindings_stack
|
|
39
|
+
exception.send :remove_instance_variable, :@__better_errors_bindings_stack
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
@exception =
|
|
34
43
|
begin
|
|
35
44
|
Marshal.dump(exception) && exception
|
|
36
|
-
rescue
|
|
45
|
+
rescue StandardError
|
|
37
46
|
UndumpableException.new(exception)
|
|
38
47
|
end
|
|
39
48
|
end
|
|
@@ -42,8 +51,11 @@ module Parallel
|
|
|
42
51
|
class Worker
|
|
43
52
|
attr_reader :pid, :read, :write
|
|
44
53
|
attr_accessor :thread
|
|
54
|
+
|
|
45
55
|
def initialize(read, write, pid)
|
|
46
|
-
@read
|
|
56
|
+
@read = read
|
|
57
|
+
@write = write
|
|
58
|
+
@pid = pid
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
def stop
|
|
@@ -70,7 +82,7 @@ module Parallel
|
|
|
70
82
|
rescue EOFError
|
|
71
83
|
raise DeadWorker
|
|
72
84
|
end
|
|
73
|
-
raise result.exception if ExceptionWrapper
|
|
85
|
+
raise result.exception if result.is_a?(ExceptionWrapper)
|
|
74
86
|
result
|
|
75
87
|
end
|
|
76
88
|
|
|
@@ -99,7 +111,7 @@ module Parallel
|
|
|
99
111
|
item, index = @mutex.synchronize do
|
|
100
112
|
return if @stopped
|
|
101
113
|
item = @lambda.call
|
|
102
|
-
@stopped = (item ==
|
|
114
|
+
@stopped = (item == Stop)
|
|
103
115
|
return if @stopped
|
|
104
116
|
[item, @index += 1]
|
|
105
117
|
end
|
|
@@ -137,7 +149,7 @@ module Parallel
|
|
|
137
149
|
end
|
|
138
150
|
|
|
139
151
|
def queue_wrapper(array)
|
|
140
|
-
array.respond_to?(:num_waiting) && array.respond_to?(:pop) &&
|
|
152
|
+
array.respond_to?(:num_waiting) && array.respond_to?(:pop) && -> { array.pop(false) }
|
|
141
153
|
end
|
|
142
154
|
end
|
|
143
155
|
|
|
@@ -153,7 +165,7 @@ module Parallel
|
|
|
153
165
|
|
|
154
166
|
if @to_be_killed.empty?
|
|
155
167
|
old_interrupt = trap_interrupt(signal) do
|
|
156
|
-
|
|
168
|
+
warn 'Parallel execution interrupted, exiting ...'
|
|
157
169
|
@to_be_killed.flatten.each { |pid| kill(pid) }
|
|
158
170
|
end
|
|
159
171
|
end
|
|
@@ -180,7 +192,7 @@ module Parallel
|
|
|
180
192
|
|
|
181
193
|
Signal.trap signal do
|
|
182
194
|
yield
|
|
183
|
-
if old == "DEFAULT"
|
|
195
|
+
if !old || old == "DEFAULT"
|
|
184
196
|
raise Interrupt
|
|
185
197
|
else
|
|
186
198
|
old.call
|
|
@@ -197,37 +209,61 @@ module Parallel
|
|
|
197
209
|
end
|
|
198
210
|
|
|
199
211
|
class << self
|
|
200
|
-
def in_threads(options={:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
def in_threads(options = { count: 2 })
|
|
213
|
+
threads = []
|
|
214
|
+
count, = extract_count_from_options(options)
|
|
215
|
+
|
|
216
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
217
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
|
218
|
+
count.times do |i|
|
|
219
|
+
threads << Thread.new { yield(i) }
|
|
220
|
+
end
|
|
221
|
+
threads.map(&:value)
|
|
222
|
+
end
|
|
223
|
+
ensure
|
|
224
|
+
threads.each(&:kill)
|
|
225
|
+
end
|
|
205
226
|
end
|
|
206
227
|
|
|
207
228
|
def in_processes(options = {}, &block)
|
|
208
229
|
count, options = extract_count_from_options(options)
|
|
209
230
|
count ||= processor_count
|
|
210
|
-
map(0...count, options.merge(:
|
|
231
|
+
map(0...count, options.merge(in_processes: count), &block)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def each(array, options = {}, &block)
|
|
235
|
+
map(array, options.merge(preserve_results: false), &block)
|
|
211
236
|
end
|
|
212
237
|
|
|
213
|
-
def
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
def any?(*args, &block)
|
|
239
|
+
raise "You must provide a block when calling #any?" if block.nil?
|
|
240
|
+
!each(*args) { |*a| raise Kill if block.call(*a) }
|
|
216
241
|
end
|
|
217
242
|
|
|
218
|
-
def
|
|
219
|
-
|
|
243
|
+
def all?(*args, &block)
|
|
244
|
+
raise "You must provide a block when calling #all?" if block.nil?
|
|
245
|
+
!!each(*args) { |*a| raise Kill unless block.call(*a) }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def each_with_index(array, options = {}, &block)
|
|
249
|
+
each(array, options.merge(with_index: true), &block)
|
|
220
250
|
end
|
|
221
251
|
|
|
222
252
|
def map(source, options = {}, &block)
|
|
253
|
+
options = options.dup
|
|
223
254
|
options[:mutex] = Mutex.new
|
|
224
255
|
|
|
225
|
-
if
|
|
256
|
+
if options[:in_processes] && options[:in_threads]
|
|
257
|
+
raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
|
|
258
|
+
elsif RUBY_PLATFORM =~ (/java/) && !(options[:in_processes])
|
|
226
259
|
method = :in_threads
|
|
227
260
|
size = options[method] || processor_count
|
|
228
261
|
elsif options[:in_threads]
|
|
229
262
|
method = :in_threads
|
|
230
263
|
size = options[method]
|
|
264
|
+
elsif options[:in_ractors]
|
|
265
|
+
method = :in_ractors
|
|
266
|
+
size = options[method]
|
|
231
267
|
else
|
|
232
268
|
method = :in_processes
|
|
233
269
|
if Process.respond_to?(:fork)
|
|
@@ -244,23 +280,78 @@ module Parallel
|
|
|
244
280
|
options[:return_results] = (options[:preserve_results] != false || !!options[:finish])
|
|
245
281
|
add_progress_bar!(job_factory, options)
|
|
246
282
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
283
|
+
result =
|
|
284
|
+
if size == 0
|
|
285
|
+
work_direct(job_factory, options, &block)
|
|
286
|
+
elsif method == :in_threads
|
|
287
|
+
work_in_threads(job_factory, options.merge(count: size), &block)
|
|
288
|
+
elsif method == :in_ractors
|
|
289
|
+
work_in_ractors(job_factory, options.merge(count: size), &block)
|
|
290
|
+
else
|
|
291
|
+
work_in_processes(job_factory, options.merge(count: size), &block)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
return result.value if result.is_a?(Break)
|
|
295
|
+
raise result if result.is_a?(Exception)
|
|
296
|
+
options[:return_results] ? result : source
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def map_with_index(array, options = {}, &block)
|
|
300
|
+
map(array, options.merge(with_index: true), &block)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def flat_map(*args, &block)
|
|
304
|
+
map(*args, &block).flatten(1)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def filter_map(*args, &block)
|
|
308
|
+
map(*args, &block).compact
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Number of physical processor cores on the current system.
|
|
312
|
+
def physical_processor_count
|
|
313
|
+
@physical_processor_count ||= begin
|
|
314
|
+
ppc =
|
|
315
|
+
case RbConfig::CONFIG["target_os"]
|
|
316
|
+
when /darwin[12]/
|
|
317
|
+
IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
|
|
318
|
+
when /linux/
|
|
319
|
+
cores = {} # unique physical ID / core ID combinations
|
|
320
|
+
phy = 0
|
|
321
|
+
File.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
|
|
322
|
+
if ln.start_with?("physical")
|
|
323
|
+
phy = ln[/\d+/]
|
|
324
|
+
elsif ln.start_with?("core")
|
|
325
|
+
cid = "#{phy}:#{ln[/\d+/]}"
|
|
326
|
+
cores[cid] = true unless cores[cid]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
cores.count
|
|
330
|
+
when /mswin|mingw/
|
|
331
|
+
require 'win32ole'
|
|
332
|
+
result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
|
|
333
|
+
"select NumberOfCores from Win32_Processor"
|
|
334
|
+
)
|
|
335
|
+
result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
|
|
336
|
+
else
|
|
337
|
+
processor_count
|
|
338
|
+
end
|
|
339
|
+
# fall back to logical count if physical info is invalid
|
|
340
|
+
ppc > 0 ? ppc : processor_count
|
|
253
341
|
end
|
|
254
342
|
end
|
|
255
343
|
|
|
256
|
-
|
|
257
|
-
|
|
344
|
+
# Number of processors seen by the OS, used for process scheduling
|
|
345
|
+
def processor_count
|
|
346
|
+
require 'etc'
|
|
347
|
+
@processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || Etc.nprocessors)
|
|
258
348
|
end
|
|
259
349
|
|
|
260
350
|
def worker_number
|
|
261
351
|
Thread.current[:parallel_worker_number]
|
|
262
352
|
end
|
|
263
353
|
|
|
354
|
+
# TODO: this does not work when doing threads in forks, so should remove and yield the number instead if needed
|
|
264
355
|
def worker_number=(worker_num)
|
|
265
356
|
Thread.current[:parallel_worker_number] = worker_num
|
|
266
357
|
end
|
|
@@ -295,13 +386,18 @@ module Parallel
|
|
|
295
386
|
def work_direct(job_factory, options, &block)
|
|
296
387
|
self.worker_number = 0
|
|
297
388
|
results = []
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
389
|
+
exception = nil
|
|
390
|
+
begin
|
|
391
|
+
while set = job_factory.next
|
|
392
|
+
item, index = set
|
|
393
|
+
results << with_instrumentation(item, index, options) do
|
|
394
|
+
call_with_index(item, index, options, &block)
|
|
395
|
+
end
|
|
302
396
|
end
|
|
397
|
+
rescue StandardError
|
|
398
|
+
exception = $!
|
|
303
399
|
end
|
|
304
|
-
results
|
|
400
|
+
exception || results
|
|
305
401
|
ensure
|
|
306
402
|
self.worker_number = nil
|
|
307
403
|
end
|
|
@@ -322,21 +418,83 @@ module Parallel
|
|
|
322
418
|
call_with_index(item, index, options, &block)
|
|
323
419
|
end
|
|
324
420
|
results_mutex.synchronize { results[index] = result }
|
|
325
|
-
rescue StandardError
|
|
326
|
-
exception =
|
|
421
|
+
rescue StandardError
|
|
422
|
+
exception = $!
|
|
327
423
|
end
|
|
328
424
|
end
|
|
329
425
|
end
|
|
330
426
|
|
|
331
|
-
|
|
427
|
+
exception || results
|
|
332
428
|
end
|
|
333
429
|
|
|
334
|
-
def
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
430
|
+
def work_in_ractors(job_factory, options)
|
|
431
|
+
exception = nil
|
|
432
|
+
results = []
|
|
433
|
+
results_mutex = Mutex.new # arrays are not thread-safe on jRuby
|
|
434
|
+
|
|
435
|
+
callback = options[:ractor]
|
|
436
|
+
if block_given? || !callback
|
|
437
|
+
raise ArgumentError, "pass the code you want to execute as `ractor: [ClassName, :method_name]`"
|
|
339
438
|
end
|
|
439
|
+
|
|
440
|
+
# build
|
|
441
|
+
ractors = Array.new(options.fetch(:count)) do
|
|
442
|
+
Ractor.new do
|
|
443
|
+
loop do
|
|
444
|
+
got = receive
|
|
445
|
+
(klass, method_name), item, index = got
|
|
446
|
+
break if index == :break
|
|
447
|
+
begin
|
|
448
|
+
Ractor.yield [nil, klass.send(method_name, item), item, index]
|
|
449
|
+
rescue StandardError => e
|
|
450
|
+
Ractor.yield [e, nil, item, index]
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# start
|
|
457
|
+
ractors.dup.each do |ractor|
|
|
458
|
+
if set = job_factory.next
|
|
459
|
+
item, index = set
|
|
460
|
+
instrument_start item, index, options
|
|
461
|
+
ractor.send [callback, item, index]
|
|
462
|
+
else
|
|
463
|
+
ractor.send([[nil, nil], nil, :break]) # stop the ractor
|
|
464
|
+
ractors.delete ractor
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# replace with new items
|
|
469
|
+
while set = job_factory.next
|
|
470
|
+
item_next, index_next = set
|
|
471
|
+
done, (exception, result, item, index) = Ractor.select(*ractors)
|
|
472
|
+
if exception
|
|
473
|
+
ractors.delete done
|
|
474
|
+
break
|
|
475
|
+
end
|
|
476
|
+
instrument_finish item, index, result, options
|
|
477
|
+
results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
|
|
478
|
+
|
|
479
|
+
instrument_start item_next, index_next, options
|
|
480
|
+
done.send([callback, item_next, index_next])
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# finish
|
|
484
|
+
ractors.each do |ractor|
|
|
485
|
+
(new_exception, result, item, index) = ractor.take
|
|
486
|
+
exception ||= new_exception
|
|
487
|
+
next if new_exception
|
|
488
|
+
instrument_finish item, index, result, options
|
|
489
|
+
results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
|
|
490
|
+
ractor.send([[nil, nil], nil, :break]) # stop the ractor
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
exception || results
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def work_in_processes(job_factory, options, &blk)
|
|
497
|
+
workers = create_workers(job_factory, options, &blk)
|
|
340
498
|
results = []
|
|
341
499
|
results_mutex = Mutex.new # arrays are not thread-safe
|
|
342
500
|
exception = nil
|
|
@@ -344,6 +502,8 @@ module Parallel
|
|
|
344
502
|
UserInterruptHandler.kill_on_ctrl_c(workers.map(&:pid), options) do
|
|
345
503
|
in_threads(options) do |i|
|
|
346
504
|
worker = workers[i]
|
|
505
|
+
worker.thread = Thread.current
|
|
506
|
+
worked = false
|
|
347
507
|
|
|
348
508
|
begin
|
|
349
509
|
loop do
|
|
@@ -352,11 +512,11 @@ module Parallel
|
|
|
352
512
|
break unless index
|
|
353
513
|
|
|
354
514
|
if options[:isolation]
|
|
355
|
-
worker = replace_worker(job_factory, workers, i, options, blk)
|
|
515
|
+
worker = replace_worker(job_factory, workers, i, options, blk) if worked
|
|
516
|
+
worked = true
|
|
517
|
+
worker.thread = Thread.current
|
|
356
518
|
end
|
|
357
519
|
|
|
358
|
-
worker.thread = Thread.current
|
|
359
|
-
|
|
360
520
|
begin
|
|
361
521
|
result = with_instrumentation item, index, options do
|
|
362
522
|
worker.work(job_factory.pack(item, index))
|
|
@@ -364,31 +524,33 @@ module Parallel
|
|
|
364
524
|
results_mutex.synchronize { results[index] = result } # arrays are not threads safe on jRuby
|
|
365
525
|
rescue StandardError => e
|
|
366
526
|
exception = e
|
|
367
|
-
if
|
|
527
|
+
if exception.is_a?(Kill)
|
|
368
528
|
(workers - [worker]).each do |w|
|
|
369
|
-
w.thread
|
|
529
|
+
w.thread&.kill
|
|
370
530
|
UserInterruptHandler.kill(w.pid)
|
|
371
531
|
end
|
|
372
532
|
end
|
|
373
533
|
end
|
|
374
534
|
end
|
|
375
535
|
ensure
|
|
376
|
-
worker.stop
|
|
536
|
+
worker.stop
|
|
377
537
|
end
|
|
378
538
|
end
|
|
379
539
|
end
|
|
380
540
|
|
|
381
|
-
|
|
541
|
+
exception || results
|
|
382
542
|
end
|
|
383
543
|
|
|
384
|
-
def replace_worker(job_factory, workers,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
544
|
+
def replace_worker(job_factory, workers, index, options, blk)
|
|
545
|
+
options[:mutex].synchronize do
|
|
546
|
+
# old worker is no longer used ... stop it
|
|
547
|
+
worker = workers[index]
|
|
548
|
+
worker.stop
|
|
388
549
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
550
|
+
# create a new replacement worker
|
|
551
|
+
running = workers - [worker]
|
|
552
|
+
workers[index] = worker(job_factory, options.merge(started_workers: running, worker_number: index), &blk)
|
|
553
|
+
end
|
|
392
554
|
end
|
|
393
555
|
|
|
394
556
|
def create_workers(job_factory, options, &block)
|
|
@@ -429,21 +591,25 @@ module Parallel
|
|
|
429
591
|
until read.eof?
|
|
430
592
|
data = Marshal.load(read)
|
|
431
593
|
item, index = job_factory.unpack(data)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
594
|
+
|
|
595
|
+
result =
|
|
596
|
+
begin
|
|
597
|
+
call_with_index(item, index, options, &block)
|
|
598
|
+
# https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
|
|
599
|
+
rescue NoMemoryError, SignalException, Interrupt, SystemExit # rubocop:disable Lint/ShadowedException
|
|
600
|
+
raise $!
|
|
601
|
+
rescue Exception # # rubocop:disable Lint/RescueException
|
|
602
|
+
ExceptionWrapper.new($!)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
begin
|
|
606
|
+
Marshal.dump(result, write)
|
|
607
|
+
rescue Errno::EPIPE
|
|
608
|
+
return # parent thread already dead
|
|
436
609
|
end
|
|
437
|
-
Marshal.dump(result, write)
|
|
438
610
|
end
|
|
439
611
|
end
|
|
440
612
|
|
|
441
|
-
def handle_exception(exception, results)
|
|
442
|
-
return nil if [Parallel::Break, Parallel::Kill].include? exception.class
|
|
443
|
-
raise exception if exception
|
|
444
|
-
results
|
|
445
|
-
end
|
|
446
|
-
|
|
447
613
|
# options is either a Integer or a Hash with :count
|
|
448
614
|
def extract_count_from_options(options)
|
|
449
615
|
if options.is_a?(Hash)
|
|
@@ -458,21 +624,53 @@ module Parallel
|
|
|
458
624
|
def call_with_index(item, index, options, &block)
|
|
459
625
|
args = [item]
|
|
460
626
|
args << index if options[:with_index]
|
|
627
|
+
results = block.call(*args)
|
|
461
628
|
if options[:return_results]
|
|
462
|
-
|
|
629
|
+
results
|
|
463
630
|
else
|
|
464
|
-
block.call(*args)
|
|
465
631
|
nil # avoid GC overhead of passing large results around
|
|
466
632
|
end
|
|
467
633
|
end
|
|
468
634
|
|
|
469
635
|
def with_instrumentation(item, index, options)
|
|
470
|
-
|
|
471
|
-
on_finish = options[:finish]
|
|
472
|
-
options[:mutex].synchronize { on_start.call(item, index) } if on_start
|
|
636
|
+
instrument_start(item, index, options)
|
|
473
637
|
result = yield
|
|
474
|
-
|
|
638
|
+
instrument_finish(item, index, result, options)
|
|
475
639
|
result unless options[:preserve_results] == false
|
|
476
640
|
end
|
|
641
|
+
|
|
642
|
+
def instrument_finish(item, index, result, options)
|
|
643
|
+
return unless (on_finish = options[:finish])
|
|
644
|
+
return instrument_finish_in_order(item, index, result, options) if options[:finish_in_order]
|
|
645
|
+
options[:mutex].synchronize { on_finish.call(item, index, result) }
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# yield results in the order of the input items
|
|
649
|
+
# needs to use `options` to store state between executions
|
|
650
|
+
# needs to use `done` index since a nil result would also be valid
|
|
651
|
+
def instrument_finish_in_order(item, index, result, options)
|
|
652
|
+
options[:mutex].synchronize do
|
|
653
|
+
# initialize our state
|
|
654
|
+
options[:finish_done] ||= []
|
|
655
|
+
options[:finish_expecting] ||= 0 # we wait for item at index 0
|
|
656
|
+
|
|
657
|
+
# store current result
|
|
658
|
+
options[:finish_done][index] = [item, result]
|
|
659
|
+
|
|
660
|
+
# yield all results that are now in order
|
|
661
|
+
break unless index == options[:finish_expecting]
|
|
662
|
+
index.upto(options[:finish_done].size).each do |i|
|
|
663
|
+
break unless (done = options[:finish_done][i])
|
|
664
|
+
options[:finish_done][i] = nil # allow GC to free this item and result
|
|
665
|
+
options[:finish].call(done[0], i, done[1])
|
|
666
|
+
options[:finish_expecting] += 1
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def instrument_start(item, index, options)
|
|
672
|
+
return unless on_start = options[:start]
|
|
673
|
+
options[:mutex].synchronize { on_start.call(item, index) }
|
|
674
|
+
end
|
|
477
675
|
end
|
|
478
676
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parallel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Grosser
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-12-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email: michael@grosser.it
|
|
@@ -18,12 +18,15 @@ extra_rdoc_files: []
|
|
|
18
18
|
files:
|
|
19
19
|
- MIT-LICENSE.txt
|
|
20
20
|
- lib/parallel.rb
|
|
21
|
-
- lib/parallel/processor_count.rb
|
|
22
21
|
- lib/parallel/version.rb
|
|
23
22
|
homepage: https://github.com/grosser/parallel
|
|
24
23
|
licenses:
|
|
25
24
|
- MIT
|
|
26
|
-
metadata:
|
|
25
|
+
metadata:
|
|
26
|
+
bug_tracker_uri: https://github.com/grosser/parallel/issues
|
|
27
|
+
documentation_uri: https://github.com/grosser/parallel/blob/v1.24.0/Readme.md
|
|
28
|
+
source_code_uri: https://github.com/grosser/parallel/tree/v1.24.0
|
|
29
|
+
wiki_uri: https://github.com/grosser/parallel/wiki
|
|
27
30
|
post_install_message:
|
|
28
31
|
rdoc_options: []
|
|
29
32
|
require_paths:
|
|
@@ -32,15 +35,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
32
35
|
requirements:
|
|
33
36
|
- - ">="
|
|
34
37
|
- !ruby/object:Gem::Version
|
|
35
|
-
version:
|
|
38
|
+
version: '2.5'
|
|
36
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
40
|
requirements:
|
|
38
41
|
- - ">="
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
43
|
version: '0'
|
|
41
44
|
requirements: []
|
|
42
|
-
|
|
43
|
-
rubygems_version: 2.4.5.1
|
|
45
|
+
rubygems_version: 3.1.6
|
|
44
46
|
signing_key:
|
|
45
47
|
specification_version: 4
|
|
46
48
|
summary: Run any kind of code in parallel processes
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
module Parallel
|
|
2
|
-
module ProcessorCount
|
|
3
|
-
# Number of processors seen by the OS and used for process scheduling.
|
|
4
|
-
#
|
|
5
|
-
# * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
|
|
6
|
-
# * BSD: /sbin/sysctl
|
|
7
|
-
# * Cygwin: /proc/cpuinfo
|
|
8
|
-
# * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
|
|
9
|
-
# * HP-UX: /usr/sbin/ioscan
|
|
10
|
-
# * IRIX: /usr/sbin/sysconf
|
|
11
|
-
# * Linux: /proc/cpuinfo
|
|
12
|
-
# * Minix 3+: /proc/cpuinfo
|
|
13
|
-
# * Solaris: /usr/sbin/psrinfo
|
|
14
|
-
# * Tru64 UNIX: /usr/sbin/psrinfo
|
|
15
|
-
# * UnixWare: /usr/sbin/psrinfo
|
|
16
|
-
#
|
|
17
|
-
def processor_count
|
|
18
|
-
@processor_count ||= begin
|
|
19
|
-
os_name = RbConfig::CONFIG["target_os"]
|
|
20
|
-
if os_name =~ /mingw|mswin/
|
|
21
|
-
require 'win32ole'
|
|
22
|
-
result = WIN32OLE.connect("winmgmts://").ExecQuery(
|
|
23
|
-
"select NumberOfLogicalProcessors from Win32_Processor")
|
|
24
|
-
result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
|
|
25
|
-
elsif File.readable?("/proc/cpuinfo")
|
|
26
|
-
IO.read("/proc/cpuinfo").scan(/^processor/).size
|
|
27
|
-
elsif File.executable?("/usr/bin/hwprefs")
|
|
28
|
-
IO.popen("/usr/bin/hwprefs thread_count").read.to_i
|
|
29
|
-
elsif File.executable?("/usr/sbin/psrinfo")
|
|
30
|
-
IO.popen("/usr/sbin/psrinfo").read.scan(/^.*on-*line/).size
|
|
31
|
-
elsif File.executable?("/usr/sbin/ioscan")
|
|
32
|
-
IO.popen("/usr/sbin/ioscan -kC processor") do |out|
|
|
33
|
-
out.read.scan(/^.*processor/).size
|
|
34
|
-
end
|
|
35
|
-
elsif File.executable?("/usr/sbin/pmcycles")
|
|
36
|
-
IO.popen("/usr/sbin/pmcycles -m").read.count("\n")
|
|
37
|
-
elsif File.executable?("/usr/sbin/lsdev")
|
|
38
|
-
IO.popen("/usr/sbin/lsdev -Cc processor -S 1").read.count("\n")
|
|
39
|
-
elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i
|
|
40
|
-
IO.popen("/usr/sbin/sysconf NPROC_ONLN").read.to_i
|
|
41
|
-
elsif File.executable?("/usr/sbin/sysctl")
|
|
42
|
-
IO.popen("/usr/sbin/sysctl -n hw.ncpu").read.to_i
|
|
43
|
-
elsif File.executable?("/sbin/sysctl")
|
|
44
|
-
IO.popen("/sbin/sysctl -n hw.ncpu").read.to_i
|
|
45
|
-
else
|
|
46
|
-
$stderr.puts "Unknown platform: " + RbConfig::CONFIG["target_os"]
|
|
47
|
-
$stderr.puts "Assuming 1 processor."
|
|
48
|
-
1
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Number of physical processor cores on the current system.
|
|
54
|
-
#
|
|
55
|
-
def physical_processor_count
|
|
56
|
-
@physical_processor_count ||= begin
|
|
57
|
-
ppc = case RbConfig::CONFIG["target_os"]
|
|
58
|
-
when /darwin1/
|
|
59
|
-
IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
|
|
60
|
-
when /linux/
|
|
61
|
-
cores = {} # unique physical ID / core ID combinations
|
|
62
|
-
phy = 0
|
|
63
|
-
IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
|
|
64
|
-
if ln.start_with?("physical")
|
|
65
|
-
phy = ln[/\d+/]
|
|
66
|
-
elsif ln.start_with?("core")
|
|
67
|
-
cid = phy + ":" + ln[/\d+/]
|
|
68
|
-
cores[cid] = true if not cores[cid]
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
cores.count
|
|
72
|
-
when /mswin|mingw/
|
|
73
|
-
require 'win32ole'
|
|
74
|
-
result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
|
|
75
|
-
"select NumberOfCores from Win32_Processor")
|
|
76
|
-
result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
|
|
77
|
-
else
|
|
78
|
-
processor_count
|
|
79
|
-
end
|
|
80
|
-
# fall back to logical count if physical info is invalid
|
|
81
|
-
ppc > 0 ? ppc : processor_count
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|