parallel 1.20.0 → 1.27.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
2
  SHA256:
3
- metadata.gz: 1f41f840e7e5128ebbb8d4ea2955b5bc555925ac049184ae1d4323885a3980b9
4
- data.tar.gz: f9463997e0b890857d1909e6df6c8632b08eb028fc4015541631ca275d1fabfb
3
+ metadata.gz: a657608c1d396b6c563f8905778d64d24c4ae6bb010e716e60a45ccc4a71295e
4
+ data.tar.gz: eb26d89f92521fed38f1bdf99f6374a2cca2e3f4c1557a7f3f92ed81d396ffb7
5
5
  SHA512:
6
- metadata.gz: '0581d59c3d38af4fefdd761264899f9661a3c28e8a4d3c9683f7e93c2295313e53d518263a6b4d04fcbb9a57eb55dfe3854e8c27f9f4e378641823eea2aada72'
7
- data.tar.gz: f77d00250586ada81bddfc2dd47e4895a526215549cee3728f31a927dd617a3a4d2bca7e72d9c7ff3c481e3a4488933c490153d0ef7dec97dc6a5cdd6bc513ed
6
+ metadata.gz: d7accf7f9b3d74e1e76f7b89db8f9040d23876e8373a6d6fa9b31e927b9ca4ce8b13330ffd888871795c79d6e3bbe7b17aa787789146ffed67f46c21fa9af3ad
7
+ data.tar.gz: ead77e379ad1c18641ca8d0457214af393beeee60fe4e5558c69d601f6f269628ce2830b6a35a7fe66e461b88fbebad33dd9bb8b767324030294e2a8f7164d8d
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Parallel
2
- VERSION = Version = '1.20.0'
3
+ VERSION = Version = '1.27.0' # rubocop:disable Naming/ConstantName
3
4
  end
data/lib/parallel.rb CHANGED
@@ -1,10 +1,8 @@
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 ProcessorCount
7
-
8
6
  Stop = Object.new.freeze
9
7
 
10
8
  class DeadWorker < StandardError
@@ -12,7 +10,9 @@ module Parallel
12
10
 
13
11
  class Break < StandardError
14
12
  attr_reader :value
13
+
15
14
  def initialize(value = nil)
15
+ super()
16
16
  @value = value
17
17
  end
18
18
  end
@@ -22,14 +22,16 @@ module Parallel
22
22
 
23
23
  class UndumpableException < StandardError
24
24
  attr_reader :backtrace
25
+
25
26
  def initialize(original)
26
- super "#{original.class}: #{original.message}"
27
+ super("#{original.class}: #{original.message}")
27
28
  @backtrace = original.backtrace
28
29
  end
29
30
  end
30
31
 
31
32
  class ExceptionWrapper
32
33
  attr_reader :exception
34
+
33
35
  def initialize(exception)
34
36
  # Remove the bindings stack added by the better_errors gem,
35
37
  # because it cannot be marshalled
@@ -40,7 +42,7 @@ module Parallel
40
42
  @exception =
41
43
  begin
42
44
  Marshal.dump(exception) && exception
43
- rescue
45
+ rescue StandardError
44
46
  UndumpableException.new(exception)
45
47
  end
46
48
  end
@@ -49,8 +51,11 @@ module Parallel
49
51
  class Worker
50
52
  attr_reader :pid, :read, :write
51
53
  attr_accessor :thread
54
+
52
55
  def initialize(read, write, pid)
53
- @read, @write, @pid = read, write, pid
56
+ @read = read
57
+ @write = write
58
+ @pid = pid
54
59
  end
55
60
 
56
61
  def stop
@@ -77,7 +82,7 @@ module Parallel
77
82
  rescue EOFError
78
83
  raise DeadWorker
79
84
  end
80
- raise result.exception if ExceptionWrapper === result
85
+ raise result.exception if result.is_a?(ExceptionWrapper)
81
86
  result
82
87
  end
83
88
 
@@ -144,7 +149,7 @@ module Parallel
144
149
  end
145
150
 
146
151
  def queue_wrapper(array)
147
- 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) }
148
153
  end
149
154
  end
150
155
 
@@ -160,7 +165,7 @@ module Parallel
160
165
 
161
166
  if @to_be_killed.empty?
162
167
  old_interrupt = trap_interrupt(signal) do
163
- $stderr.puts 'Parallel execution interrupted, exiting ...'
168
+ warn 'Parallel execution interrupted, exiting ...'
164
169
  @to_be_killed.flatten.each { |pid| kill(pid) }
165
170
  end
166
171
  end
@@ -204,32 +209,30 @@ module Parallel
204
209
  end
205
210
 
206
211
  class << self
207
- def in_threads(options={:count => 2})
212
+ def in_threads(options = { count: 2 })
208
213
  threads = []
209
- count, _ = extract_count_from_options(options)
214
+ count, = extract_count_from_options(options)
210
215
 
211
216
  Thread.handle_interrupt(Exception => :never) do
212
- begin
213
- Thread.handle_interrupt(Exception => :immediate) do
214
- count.times do |i|
215
- threads << Thread.new { yield(i) }
216
- end
217
- threads.map(&:value)
217
+ Thread.handle_interrupt(Exception => :immediate) do
218
+ count.times do |i|
219
+ threads << Thread.new { yield(i) }
218
220
  end
219
- ensure
220
- threads.each(&:kill)
221
+ threads.map(&:value)
221
222
  end
223
+ ensure
224
+ threads.each(&:kill)
222
225
  end
223
226
  end
224
227
 
225
228
  def in_processes(options = {}, &block)
226
229
  count, options = extract_count_from_options(options)
227
230
  count ||= processor_count
228
- map(0...count, options.merge(:in_processes => count), &block)
231
+ map(0...count, options.merge(in_processes: count), &block)
229
232
  end
230
233
 
231
- def each(array, options={}, &block)
232
- map(array, options.merge(:preserve_results => false), &block)
234
+ def each(array, options = {}, &block)
235
+ map(array, options.merge(preserve_results: false), &block)
233
236
  end
234
237
 
235
238
  def any?(*args, &block)
@@ -242,8 +245,8 @@ module Parallel
242
245
  !!each(*args) { |*a| raise Kill unless block.call(*a) }
243
246
  end
244
247
 
245
- def each_with_index(array, options={}, &block)
246
- each(array, options.merge(:with_index => true), &block)
248
+ def each_with_index(array, options = {}, &block)
249
+ each(array, options.merge(with_index: true), &block)
247
250
  end
248
251
 
249
252
  def map(source, options = {}, &block)
@@ -251,13 +254,16 @@ module Parallel
251
254
  options[:mutex] = Mutex.new
252
255
 
253
256
  if options[:in_processes] && options[:in_threads]
254
- raise ArgumentError.new("Please specify only one of `in_processes` or `in_threads`.")
255
- elsif RUBY_PLATFORM =~ /java/ and not options[:in_processes]
257
+ raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
258
+ elsif RUBY_PLATFORM =~ (/java/) && !options[:in_processes]
256
259
  method = :in_threads
257
260
  size = options[method] || processor_count
258
261
  elsif options[:in_threads]
259
262
  method = :in_threads
260
263
  size = options[method]
264
+ elsif options[:in_ractors]
265
+ method = :in_ractors
266
+ size = options[method]
261
267
  else
262
268
  method = :in_processes
263
269
  if Process.respond_to?(:fork)
@@ -278,9 +284,11 @@ module Parallel
278
284
  if size == 0
279
285
  work_direct(job_factory, options, &block)
280
286
  elsif method == :in_threads
281
- work_in_threads(job_factory, options.merge(:count => size), &block)
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)
282
290
  else
283
- work_in_processes(job_factory, options.merge(:count => size), &block)
291
+ work_in_processes(job_factory, options.merge(count: size), &block)
284
292
  end
285
293
 
286
294
  return result.value if result.is_a?(Break)
@@ -288,12 +296,51 @@ module Parallel
288
296
  options[:return_results] ? result : source
289
297
  end
290
298
 
291
- def map_with_index(array, options={}, &block)
292
- map(array, options.merge(:with_index => true), &block)
299
+ def map_with_index(array, options = {}, &block)
300
+ map(array, options.merge(with_index: true), &block)
301
+ end
302
+
303
+ def flat_map(...)
304
+ map(...).flatten(1)
305
+ end
306
+
307
+ def filter_map(...)
308
+ map(...).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
+ physical_processor_count_windows
332
+ else
333
+ processor_count
334
+ end
335
+ # fall back to logical count if physical info is invalid
336
+ ppc > 0 ? ppc : processor_count
337
+ end
293
338
  end
294
339
 
295
- def flat_map(*args, &block)
296
- map(*args, &block).flatten(1)
340
+ # Number of processors seen by the OS or value considering CPU quota if the process is inside a cgroup,
341
+ # used for process scheduling
342
+ def processor_count
343
+ @processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || available_processor_count)
297
344
  end
298
345
 
299
346
  def worker_number
@@ -307,8 +354,35 @@ module Parallel
307
354
 
308
355
  private
309
356
 
357
+ def physical_processor_count_windows
358
+ # Get-CimInstance introduced in PowerShell 3 or earlier: https://learn.microsoft.com/en-us/previous-versions/powershell/module/cimcmdlets/get-ciminstance?view=powershell-3.0
359
+ result = run(
360
+ 'powershell -command "Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores ' \
361
+ '| Select-Object -Property NumberOfCores"'
362
+ )
363
+ if !result || $?.exitstatus != 0
364
+ # fallback to deprecated wmic for older systems
365
+ result = run("wmic cpu get NumberOfCores")
366
+ end
367
+ if !result || $?.exitstatus != 0
368
+ # Bail out if both commands returned something unexpected
369
+ warn "guessing pyhsical processor count"
370
+ processor_count
371
+ else
372
+ # powershell: "\nNumberOfCores\n-------------\n 4\n\n\n"
373
+ # wmic: "NumberOfCores \n\n4 \n\n\n\n"
374
+ result.scan(/\d+/).map(&:to_i).reduce(:+)
375
+ end
376
+ end
377
+
378
+ def run(command)
379
+ IO.popen(command, &:read)
380
+ rescue Errno::ENOENT
381
+ # Ignore
382
+ end
383
+
310
384
  def add_progress_bar!(job_factory, options)
311
- if progress_options = options[:progress]
385
+ if (progress_options = options[:progress])
312
386
  raise "Progressbar can only be used with array like items" if job_factory.size == Float::INFINITY
313
387
  require 'ruby-progressbar'
314
388
 
@@ -337,13 +411,13 @@ module Parallel
337
411
  results = []
338
412
  exception = nil
339
413
  begin
340
- while set = job_factory.next
414
+ while (set = job_factory.next)
341
415
  item, index = set
342
416
  results << with_instrumentation(item, index, options) do
343
417
  call_with_index(item, index, options, &block)
344
418
  end
345
419
  end
346
- rescue
420
+ rescue StandardError
347
421
  exception = $!
348
422
  end
349
423
  exception || results
@@ -360,14 +434,14 @@ module Parallel
360
434
  in_threads(options) do |worker_num|
361
435
  self.worker_number = worker_num
362
436
  # as long as there are more jobs, work on one of them
363
- while !exception && set = job_factory.next
437
+ while !exception && (set = job_factory.next)
364
438
  begin
365
439
  item, index = set
366
440
  result = with_instrumentation item, index, options do
367
441
  call_with_index(item, index, options, &block)
368
442
  end
369
443
  results_mutex.synchronize { results[index] = result }
370
- rescue
444
+ rescue StandardError
371
445
  exception = $!
372
446
  end
373
447
  end
@@ -376,6 +450,72 @@ module Parallel
376
450
  exception || results
377
451
  end
378
452
 
453
+ def work_in_ractors(job_factory, options)
454
+ exception = nil
455
+ results = []
456
+ results_mutex = Mutex.new # arrays are not thread-safe on jRuby
457
+
458
+ callback = options[:ractor]
459
+ if block_given? || !callback
460
+ raise ArgumentError, "pass the code you want to execute as `ractor: [ClassName, :method_name]`"
461
+ end
462
+
463
+ # build
464
+ ractors = Array.new(options.fetch(:count)) do
465
+ Ractor.new do
466
+ loop do
467
+ got = receive
468
+ (klass, method_name), item, index = got
469
+ break if index == :break
470
+ begin
471
+ Ractor.yield [nil, klass.send(method_name, item), item, index]
472
+ rescue StandardError => e
473
+ Ractor.yield [e, nil, item, index]
474
+ end
475
+ end
476
+ end
477
+ end
478
+
479
+ # start
480
+ ractors.dup.each do |ractor|
481
+ if (set = job_factory.next)
482
+ item, index = set
483
+ instrument_start item, index, options
484
+ ractor.send [callback, item, index]
485
+ else
486
+ ractor.send([[nil, nil], nil, :break]) # stop the ractor
487
+ ractors.delete ractor
488
+ end
489
+ end
490
+
491
+ # replace with new items
492
+ while (set = job_factory.next)
493
+ item_next, index_next = set
494
+ done, (exception, result, item, index) = Ractor.select(*ractors)
495
+ if exception
496
+ ractors.delete done
497
+ break
498
+ end
499
+ instrument_finish item, index, result, options
500
+ results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
501
+
502
+ instrument_start item_next, index_next, options
503
+ done.send([callback, item_next, index_next])
504
+ end
505
+
506
+ # finish
507
+ ractors.each do |ractor|
508
+ (new_exception, result, item, index) = ractor.take
509
+ exception ||= new_exception
510
+ next if new_exception
511
+ instrument_finish item, index, result, options
512
+ results_mutex.synchronize { results[index] = (options[:preserve_results] == false ? nil : result) }
513
+ ractor.send([[nil, nil], nil, :break]) # stop the ractor
514
+ end
515
+
516
+ exception || results
517
+ end
518
+
379
519
  def work_in_processes(job_factory, options, &blk)
380
520
  workers = create_workers(job_factory, options, &blk)
381
521
  results = []
@@ -407,9 +547,9 @@ module Parallel
407
547
  results_mutex.synchronize { results[index] = result } # arrays are not threads safe on jRuby
408
548
  rescue StandardError => e
409
549
  exception = e
410
- if Kill === exception
550
+ if exception.is_a?(Kill)
411
551
  (workers - [worker]).each do |w|
412
- w.thread.kill if w.thread
552
+ w.thread&.kill
413
553
  UserInterruptHandler.kill(w.pid)
414
554
  end
415
555
  end
@@ -420,18 +560,19 @@ module Parallel
420
560
  end
421
561
  end
422
562
  end
563
+
423
564
  exception || results
424
565
  end
425
566
 
426
- def replace_worker(job_factory, workers, i, options, blk)
567
+ def replace_worker(job_factory, workers, index, options, blk)
427
568
  options[:mutex].synchronize do
428
569
  # old worker is no longer used ... stop it
429
- worker = workers[i]
570
+ worker = workers[index]
430
571
  worker.stop
431
572
 
432
573
  # create a new replacement worker
433
574
  running = workers - [worker]
434
- workers[i] = worker(job_factory, options.merge(started_workers: running, worker_number: i), &blk)
575
+ workers[index] = worker(job_factory, options.merge(started_workers: running, worker_number: index), &blk)
435
576
  end
436
577
  end
437
578
 
@@ -473,14 +614,17 @@ module Parallel
473
614
  until read.eof?
474
615
  data = Marshal.load(read)
475
616
  item, index = job_factory.unpack(data)
476
- result = begin
477
- call_with_index(item, index, options, &block)
478
- # https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
479
- rescue NoMemoryError, SignalException, Interrupt, SystemExit
480
- raise $!
481
- rescue Exception
482
- ExceptionWrapper.new($!)
483
- end
617
+
618
+ result =
619
+ begin
620
+ call_with_index(item, index, options, &block)
621
+ # https://github.com/rspec/rspec-support/blob/673133cdd13b17077b3d88ece8d7380821f8d7dc/lib/rspec/support.rb#L132-L140
622
+ rescue NoMemoryError, SignalException, Interrupt, SystemExit # rubocop:disable Lint/ShadowedException
623
+ raise $!
624
+ rescue Exception # # rubocop:disable Lint/RescueException
625
+ ExceptionWrapper.new($!)
626
+ end
627
+
484
628
  begin
485
629
  Marshal.dump(result, write)
486
630
  rescue Errno::EPIPE
@@ -503,21 +647,62 @@ module Parallel
503
647
  def call_with_index(item, index, options, &block)
504
648
  args = [item]
505
649
  args << index if options[:with_index]
650
+ results = block.call(*args)
506
651
  if options[:return_results]
507
- block.call(*args)
652
+ results
508
653
  else
509
- block.call(*args)
510
654
  nil # avoid GC overhead of passing large results around
511
655
  end
512
656
  end
513
657
 
514
658
  def with_instrumentation(item, index, options)
515
- on_start = options[:start]
516
- on_finish = options[:finish]
517
- options[:mutex].synchronize { on_start.call(item, index) } if on_start
659
+ instrument_start(item, index, options)
518
660
  result = yield
519
- options[:mutex].synchronize { on_finish.call(item, index, result) } if on_finish
661
+ instrument_finish(item, index, result, options)
520
662
  result unless options[:preserve_results] == false
521
663
  end
664
+
665
+ def instrument_finish(item, index, result, options)
666
+ return unless (on_finish = options[:finish])
667
+ return instrument_finish_in_order(item, index, result, options) if options[:finish_in_order]
668
+ options[:mutex].synchronize { on_finish.call(item, index, result) }
669
+ end
670
+
671
+ # yield results in the order of the input items
672
+ # needs to use `options` to store state between executions
673
+ # needs to use `done` index since a nil result would also be valid
674
+ def instrument_finish_in_order(item, index, result, options)
675
+ options[:mutex].synchronize do
676
+ # initialize our state
677
+ options[:finish_done] ||= []
678
+ options[:finish_expecting] ||= 0 # we wait for item at index 0
679
+
680
+ # store current result
681
+ options[:finish_done][index] = [item, result]
682
+
683
+ # yield all results that are now in order
684
+ break unless index == options[:finish_expecting]
685
+ index.upto(options[:finish_done].size).each do |i|
686
+ break unless (done = options[:finish_done][i])
687
+ options[:finish_done][i] = nil # allow GC to free this item and result
688
+ options[:finish].call(done[0], i, done[1])
689
+ options[:finish_expecting] += 1
690
+ end
691
+ end
692
+ end
693
+
694
+ def instrument_start(item, index, options)
695
+ return unless (on_start = options[:start])
696
+ options[:mutex].synchronize { on_start.call(item, index) }
697
+ end
698
+
699
+ def available_processor_count
700
+ gem 'concurrent-ruby', '>= 1.3.4'
701
+ require 'concurrent-ruby'
702
+ Concurrent.available_processor_count.floor
703
+ rescue LoadError
704
+ require 'etc'
705
+ Etc.nprocessors
706
+ end
522
707
  end
523
708
  end
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-11-07 00:00:00.000000000 Z
10
+ date: 2025-04-14 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email: michael@grosser.it
15
13
  executables: []
16
14
  extensions: []
@@ -18,17 +16,15 @@ extra_rdoc_files: []
18
16
  files:
19
17
  - MIT-LICENSE.txt
20
18
  - lib/parallel.rb
21
- - lib/parallel/processor_count.rb
22
19
  - lib/parallel/version.rb
23
20
  homepage: https://github.com/grosser/parallel
24
21
  licenses:
25
22
  - MIT
26
23
  metadata:
27
24
  bug_tracker_uri: https://github.com/grosser/parallel/issues
28
- documentation_uri: https://github.com/grosser/parallel/blob/v1.20.0/Readme.md
29
- source_code_uri: https://github.com/grosser/parallel/tree/v1.20.0
25
+ documentation_uri: https://github.com/grosser/parallel/blob/v1.27.0/Readme.md
26
+ source_code_uri: https://github.com/grosser/parallel/tree/v1.27.0
30
27
  wiki_uri: https://github.com/grosser/parallel/wiki
31
- post_install_message:
32
28
  rdoc_options: []
33
29
  require_paths:
34
30
  - lib
@@ -36,15 +32,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
32
  requirements:
37
33
  - - ">="
38
34
  - !ruby/object:Gem::Version
39
- version: '2.5'
35
+ version: '2.7'
40
36
  required_rubygems_version: !ruby/object:Gem::Requirement
41
37
  requirements:
42
38
  - - ">="
43
39
  - !ruby/object:Gem::Version
44
40
  version: '0'
45
41
  requirements: []
46
- rubygems_version: 3.1.3
47
- signing_key:
42
+ rubygems_version: 3.6.2
48
43
  specification_version: 4
49
44
  summary: Run any kind of code in parallel processes
50
45
  test_files: []
@@ -1,42 +0,0 @@
1
- require 'etc'
2
-
3
- module Parallel
4
- # TODO: inline this method into parallel.rb and kill physical_processor_count in next major release
5
- module ProcessorCount
6
- # Number of processors seen by the OS, used for process scheduling
7
- def processor_count
8
- @processor_count ||= Integer(ENV['PARALLEL_PROCESSOR_COUNT'] || Etc.nprocessors)
9
- end
10
-
11
- # Number of physical processor cores on the current system.
12
- def physical_processor_count
13
- @physical_processor_count ||= begin
14
- ppc = case RbConfig::CONFIG["target_os"]
15
- when /darwin1/
16
- IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
17
- when /linux/
18
- cores = {} # unique physical ID / core ID combinations
19
- phy = 0
20
- IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
21
- if ln.start_with?("physical")
22
- phy = ln[/\d+/]
23
- elsif ln.start_with?("core")
24
- cid = phy + ":" + ln[/\d+/]
25
- cores[cid] = true if not cores[cid]
26
- end
27
- end
28
- cores.count
29
- when /mswin|mingw/
30
- require 'win32ole'
31
- result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
32
- "select NumberOfCores from Win32_Processor")
33
- result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
34
- else
35
- processor_count
36
- end
37
- # fall back to logical count if physical info is invalid
38
- ppc > 0 ? ppc : processor_count
39
- end
40
- end
41
- end
42
- end