#!/usr/bin/env sage-bootstrap-python # # Determine the number of threads to be used by Sage. # # Outputs three space-separated numbers: # 1) The number of threads to use for Sage, based on MAKE, MAKEFLAGS # and SAGE_NUM_THREADS # 2) The number of threads to use when parallel execution is explicitly # asked for (e.g. sage -tp) # 3) The number of CPU cores in the system, as determined by # multiprocessing.cpu_count() # # AUTHOR: Jeroen Demeyer (2011-12-08): Github issue #12016 # from __future__ import print_function import os import multiprocessing import re import math def number_of_cores(): """ Try to determine the number of CPU cores in this system. If successful return that number. Otherwise return 1. """ # If the environment variable SAGE_NUM_CORES exists, use that value. # This is useful for testing purposes. try: n = int(os.environ["SAGE_NUM_CORES"]) if n > 0: return n except (ValueError, KeyError): pass try: n = multiprocessing.cpu_count() if n > 0: return n except NotImplementedError: pass try: # Solaris fix from subprocess import Popen, PIPE p = Popen(['sysctl', '-n', 'hw.ncpu'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) n = int(p.stdout.read().strip()) if n > 0: return n except (ValueError, OSError): pass return 1 def parse_jobs_from_MAKE(MAKE, unlimited=999999): """ Parse an environment variable like :envvar:`MAKE` for the number of jobs specified. This looks at arguments ``-j``, ``--jobs``, ``-l``, ``--load-average``. INPUT: - ``MAKE`` -- The value of :envvar:`MAKE` or :envvar:`MAKEFLAGS`. - ``unlimited`` -- The value to return when ``MAKE`` contains ``-j`` without argument and no ``-l`` option. Normally this is interpreted as "unlimited". OUTPUT: The number of jobs specified by that variable. Raise ``KeyError`` if no number of jobs is specified in ``MAKE``. """ # First, find value of -j # Since this is doing a greedy match on the left and non-greedy on the right, # we find the last -j or --jobs (j, num) = re.subn(r'^(.* )?(-j *|--jobs(=(?=[0-9])| +))([0-9]*)( .*?)?$', r'\4', MAKE, count=1) if num < 1: # No replacement done, i.e. no -j option found raise KeyError("No number of jobs specified") elif j == "": # j is empty: unlimited number of jobs! :-) j = unlimited else: j = int(j) if j <= 0: raise ValueError("Non-positive value specified for -j") # Next, find the value of -l # If it is specified, use this as an upper bound on j (l, num) = re.subn(r'^(.* )?(-l *|--(load-average|max-load)(=(?=[0-9])| +))([0-9.]*)( .*?)?$', r'\5', MAKE, count=1) if num < 1: # No replacement done, i.e. no -l option found pass elif not l: # No load limit specified pass else: l = int(math.ceil(float(l))) # A load limit will never prevent starting at least one job if l <= 1: l = 1 j = min(j, l) return j def num_threads(): """ Determine the number of threads from the environment variables (in decreasing priority) :envvar:`SAGE_NUM_THREADS`, :envvar:`MAKE`, :envvar:`MAKEFLAGS` and :envvar:`MFLAGS`. If :envvar:`SAGE_NUM_THREADS` is 0 and neither :envvar:`MAKE` nor :envvar:`MAKEFLAGS` specifies a number of jobs, the use a default of ``min(8, number_of_cores)``. OUTPUT: a tuple (num_threads, num_threads_parallel, num_cores) """ num_cores = number_of_cores() num_threads = None # Handle MFLAGS only for backwards compatibility try: num_threads = parse_jobs_from_MAKE(os.environ["MFLAGS"], unlimited=2) except (ValueError, KeyError): pass try: # Prepend hyphen to MAKEFLAGS if it does not start with one MAKEFLAGS = os.environ["MAKEFLAGS"] if MAKEFLAGS[0] != '-': MAKEFLAGS = '-' + MAKEFLAGS # In MAKEFLAGS, "-j" does not mean unlimited. It probably # means an inherited number of jobs, let us use 2 for safety. num_threads = parse_jobs_from_MAKE(MAKEFLAGS, unlimited=2) except (ValueError, KeyError, IndexError): pass try: num_threads = parse_jobs_from_MAKE(os.environ["MAKE"]) except (ValueError, KeyError): pass # Number of threads to use when parallel execution is explicitly # asked for num_threads_parallel = num_threads if num_threads_parallel is None: num_threads_parallel = max(min(8, num_cores), 2) try: sage_num_threads = int(os.environ["SAGE_NUM_THREADS"]) if sage_num_threads == 0: # If SAGE_NUM_THREADS is 0, use the default only # if none of the above variables specified anything. if num_threads is None: num_threads = min(8, num_cores) elif sage_num_threads > 0: # SAGE_NUM_THREADS overrides everything num_threads = sage_num_threads num_threads_parallel = sage_num_threads except (ValueError, KeyError): pass # If we still don't know, use 1 thread if num_threads is None: num_threads = 1 # Finally, use SAGE_NUM_THREADS_PARALLEL if set. A user isn't # likely to set this, but it ensures that sage-env is idempotent # if called more than once. try: sage_num_threads = int(os.environ["SAGE_NUM_THREADS_PARALLEL"]) if sage_num_threads > 0: num_threads_parallel = sage_num_threads except (ValueError, KeyError): pass return (num_threads, num_threads_parallel, num_cores) print(*num_threads())