require 'etc' require 'rbconfig' require 'concurrent/delay' module Concurrent module Utility # @!visibility private class ProcessorCounter def initialize @processor_count = Delay.new { compute_processor_count } @physical_processor_count = Delay.new { compute_physical_processor_count } end # Number of processors seen by the OS and used for process scheduling. For # performance reasons the calculated value will be memoized on the first # call. # # When running under JRuby the Java runtime call # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According # to the Java documentation this "value may change during a particular # invocation of the virtual machine... [applications] should therefore # occasionally poll this property." Subsequently the result will NOT be # memoized under JRuby. # # Ruby's Etc.nprocessors will be used if available (MRI 2.2+). # # On Windows the Win32 API will be queried for the # `NumberOfLogicalProcessors from Win32_Processor`. This will return the # total number "logical processors for the current instance of the # processor", which taked into account hyperthreading. # # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev # * Alpha: /usr/bin/nproc (/proc/cpuinfo exists but cannot be used) # * BSD: /sbin/sysctl # * Cygwin: /proc/cpuinfo # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl # * HP-UX: /usr/sbin/ioscan # * IRIX: /usr/sbin/sysconf # * Linux: /proc/cpuinfo # * Minix 3+: /proc/cpuinfo # * Solaris: /usr/sbin/psrinfo # * Tru64 UNIX: /usr/sbin/psrinfo # * UnixWare: /usr/sbin/psrinfo # # @return [Integer] number of processors seen by the OS or Java runtime # # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb # # @see http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html#availableProcessors() # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx def processor_count @processor_count.value end # Number of physical processor cores on the current system. For performance # reasons the calculated value will be memoized on the first call. # # On Windows the Win32 API will be queried for the `NumberOfCores from # Win32_Processor`. This will return the total number "of cores for the # current instance of the processor." On Unix-like operating systems either # the `hwprefs` or `sysctl` utility will be called in a subshell and the # returned value will be used. In the rare case where none of these methods # work or an exception is raised the function will simply return 1. # # @return [Integer] number physical processor cores on the current system # # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb # # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx # @see http://www.unix.com/man-page/osx/1/HWPREFS/ # @see http://linux.die.net/man/8/sysctl def physical_processor_count @physical_processor_count.value end private def compute_processor_count if Concurrent.on_jruby? java.lang.Runtime.getRuntime.availableProcessors elsif Etc.respond_to?(:nprocessors) && (nprocessor = Etc.nprocessors rescue nil) nprocessor else os_name = RbConfig::CONFIG["target_os"] if os_name =~ /mingw|mswin/ require 'win32ole' result = WIN32OLE.connect("winmgmts://").ExecQuery( "select NumberOfLogicalProcessors from Win32_Processor") result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+) elsif File.readable?("/proc/cpuinfo") && (cpuinfo_count = IO.read("/proc/cpuinfo").scan(/^processor/).size) > 0 cpuinfo_count elsif File.executable?("/usr/bin/nproc") IO.popen("/usr/bin/nproc --all", &:read).to_i elsif File.executable?("/usr/bin/hwprefs") IO.popen("/usr/bin/hwprefs thread_count", &:read).to_i elsif File.executable?("/usr/sbin/psrinfo") IO.popen("/usr/sbin/psrinfo", &:read).scan(/^.*on-*line/).size elsif File.executable?("/usr/sbin/ioscan") IO.popen("/usr/sbin/ioscan -kC processor", &:read).scan(/^.*processor/).size elsif File.executable?("/usr/sbin/pmcycles") IO.popen("/usr/sbin/pmcycles -m", &:read).count("\n") elsif File.executable?("/usr/sbin/lsdev") IO.popen("/usr/sbin/lsdev -Cc processor -S 1", &:read).count("\n") elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i IO.popen("/usr/sbin/sysconf NPROC_ONLN", &:read).to_i elsif File.executable?("/usr/sbin/sysctl") IO.popen("/usr/sbin/sysctl -n hw.ncpu", &:read).to_i elsif File.executable?("/sbin/sysctl") IO.popen("/sbin/sysctl -n hw.ncpu", &:read).to_i else # TODO (pitr-ch 05-Nov-2016): warn about failures 1 end end rescue return 1 end def compute_physical_processor_count ppc = case RbConfig::CONFIG["target_os"] when /darwin1/ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu", &:read).to_i when /linux/ cores = {} # unique physical ID / core ID combinations phy = 0 IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln| if ln.start_with?("physical") phy = ln[/\d+/] elsif ln.start_with?("core") cid = phy + ":" + ln[/\d+/] cores[cid] = true if not cores[cid] end end cores.count when /mswin|mingw/ require 'win32ole' result_set = WIN32OLE.connect("winmgmts://").ExecQuery( "select NumberOfCores from Win32_Processor") result_set.to_enum.collect(&:NumberOfCores).reduce(:+) else processor_count end # fall back to logical count if physical info is invalid ppc > 0 ? ppc : processor_count rescue return 1 end end end # create the default ProcessorCounter on load @processor_counter = Utility::ProcessorCounter.new singleton_class.send :attr_reader, :processor_counter def self.processor_count processor_counter.processor_count end def self.physical_processor_count processor_counter.physical_processor_count end end