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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bbf5d8fc78e3bdf6f1d12ae80ffff41fa5e6d0ed
4
- data.tar.gz: c1eb1b447d1c59dce453710d943d2b952d1079b3
2
+ SHA256:
3
+ metadata.gz: f7b4a5662ef68724c75c2d655d9bd09ad109edc02d3199a0744093544608f74b
4
+ data.tar.gz: 247f7f3745c42cdafeb5d403fff250e99b99a56d4dc4948050dcf20ecb60d183
5
5
  SHA512:
6
- metadata.gz: 0fbe56fc6a4ba0ec2df7bbdb9f16e44b08e42fc0bced3ff939a7baa128940d0afc6ae27da216ffb78dc066f55989e779198cb39137366e6988ab57ba854681cf
7
- data.tar.gz: 1b5252fb4ec4ef143072dbd604641a5d614e16142065f1a1618f665cbc2f74074305adef81e784587ec6a582662bfde67654e0e431850cf9e31dbf0b8082b922
6
+ metadata.gz: 18f62ea9fefb30bda9b89f0189074da99572c5c17fb93ba28e553b0eb9f6d7a61bd70ba423004b138b05c3c86f8e04fd32fce81da40a68c0bdca39e263b8b892
7
+ data.tar.gz: dca61c9e3cab22de0ad064c96c55afc88177214108d6bdbfb8e9a93343f23d6ee321c83aa6c4cfa9ee33da79d01802d7b952f3dd0360f9cca26e5da78a4117fa
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Parallel
2
- VERSION = Version = '1.10.0'
3
+ VERSION = Version = '1.24.0' # rubocop:disable Naming/ConstantName
3
4
  end
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
- extend Parallel::ProcessorCount
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 < StandardError
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
- @bracktrace = original.backtrace
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, @write, @pid = read, write, pid
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 === result
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 == Parallel::Stop)
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) && lambda { array.pop(false) }
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
- $stderr.puts 'Parallel execution interrupted, exiting ...'
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={:count => 2})
201
- count, _ = extract_count_from_options(options)
202
- Array.new(count).each_with_index.map do |_, i|
203
- Thread.new { yield(i) }
204
- end.map!(&:value)
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(:in_processes => count), &block)
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 each(array, options={}, &block)
214
- map(array, options.merge(:preserve_results => false), &block)
215
- array
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 each_with_index(array, options={}, &block)
219
- each(array, options.merge(:with_index => true), &block)
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 RUBY_PLATFORM =~ /java/ and not options[:in_processes]
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
- if size == 0
248
- work_direct(job_factory, options, &block)
249
- elsif method == :in_threads
250
- work_in_threads(job_factory, options.merge(:count => size), &block)
251
- else
252
- work_in_processes(job_factory, options.merge(:count => size), &block)
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
- def map_with_index(array, options={}, &block)
257
- map(array, options.merge(:with_index => true), &block)
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
- while set = job_factory.next
299
- item, index = set
300
- results << with_instrumentation(item, index, options) do
301
- call_with_index(item, index, options, &block)
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 => e
326
- exception = e
421
+ rescue StandardError
422
+ exception = $!
327
423
  end
328
424
  end
329
425
  end
330
426
 
331
- handle_exception(exception, results)
427
+ exception || results
332
428
  end
333
429
 
334
- def work_in_processes(job_factory, options, &blk)
335
- workers = if options[:isolation]
336
- [] # we create workers per job and not beforehand
337
- else
338
- create_workers(job_factory, options, &blk)
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 Parallel::Kill === exception
527
+ if exception.is_a?(Kill)
368
528
  (workers - [worker]).each do |w|
369
- w.thread.kill unless w.thread.nil?
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 if worker
536
+ worker.stop
377
537
  end
378
538
  end
379
539
  end
380
540
 
381
- handle_exception(exception, results)
541
+ exception || results
382
542
  end
383
543
 
384
- def replace_worker(job_factory, workers, i, options, blk)
385
- # old worker is no longer used ... stop it
386
- worker = workers[i]
387
- worker.stop if worker
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
- # create a new replacement worker
390
- running = workers - [worker]
391
- workers[i] = worker(job_factory, options.merge(started_workers: running, worker_number: i), &blk)
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
- result = begin
433
- call_with_index(item, index, options, &block)
434
- rescue StandardError => e
435
- ExceptionWrapper.new(e)
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
- block.call(*args)
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
- on_start = options[:start]
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
- options[:mutex].synchronize { on_finish.call(item, index, result) } if on_finish
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.10.0
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: 2016-11-24 00:00:00.000000000 Z
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: 1.9.3
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
- rubyforge_project:
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