Cap the maximum number of build jobs (#11373)

* config:build_jobs now controls the number of parallel jobs to spawn during
builds, but cannot ever exceed the number of cores on the machine.

* The default is set to 16 or the number of available cores, whatever
is lowest.

* Updated docs to reflect the changes done to limit parallel builds
This commit is contained in:
Massimiliano Culpo 2019-05-28 15:42:04 +02:00 committed by Todd Gamblin
parent 2a51e07fde
commit 01ece824e1
6 changed files with 55 additions and 20 deletions

View file

@ -99,10 +99,12 @@ config:
locks: true
# The default number of jobs to use when running `make` in parallel.
# If set to 4, for example, `spack install` will run `make -j4`.
# If not set, all available cores are used by default.
# build_jobs: 4
# The maximum number of jobs to use when running `make` in parallel,
# always limited by the number of cores available. For instance:
# - If set to 16 on a 4 cores machine `spack install` will run `make -j4`
# - If set to 16 on a 18 cores machine `spack install` will run `make -j16`
# If not set, Spack will use all available cores up to 16.
# build_jobs: 16
# If set to true, Spack will use ccache to cache C compiles.

View file

@ -178,16 +178,23 @@ set ``dirty`` to ``true`` to skip the cleaning step and make all builds
"dirty" by default. Be aware that this will reduce the reproducibility
of builds.
.. _build-jobs:
--------------
``build_jobs``
--------------
Unless overridden in a package or on the command line, Spack builds all
packages in parallel. For a build system that uses Makefiles, this means
running ``make -j<build_jobs>``, where ``build_jobs`` is the number of
threads to use.
packages in parallel. The default parallelism is equal to the number of
cores on your machine, up to 16. Parallelism cannot exceed the number of
cores available on the host. For a build system that uses Makefiles, this
means running:
- ``make -j<build_jobs>``, when ``build_jobs`` is less than the number of
cores on the machine
- ``make -j<ncores>``, when ``build_jobs`` is greater or equal to the
number of cores on the machine
The default parallelism is equal to the number of cores on your machine.
If you work on a shared login node or have a strict ulimit, it may be
necessary to set the default to a lower value. By setting ``build_jobs``
to 4, for example, commands like ``spack install`` will run ``make -j4``

View file

@ -1713,12 +1713,11 @@ RPATHs in Spack are handled in one of three ways:
Parallel builds
---------------
By default, Spack will invoke ``make()`` with a ``-j <njobs>``
argument, so that builds run in parallel. It figures out how many
jobs to run by determining how many cores are on the host machine.
Specifically, it uses the number of CPUs reported by Python's
`multiprocessing.cpu_count()
<http://docs.python.org/library/multiprocessing.html#multiprocessing.cpu_count>`_.
By default, Spack will invoke ``make()``, or any other similar tool,
with a ``-j <njobs>`` argument, so that builds run in parallel.
The parallelism is determined by the value of the ``build_jobs`` entry
in ``config.yaml`` (see :ref:`here <build-jobs>` for more details on
how this value is computed).
If a package does not build properly in parallel, you can override
this setting by adding ``parallel = False`` to your package. For

View file

@ -5,6 +5,7 @@
import argparse
import multiprocessing
import spack.cmd
import spack.config
@ -86,6 +87,7 @@ def __call__(self, parser, namespace, jobs, option_string):
'[expected a positive integer, got "{1}"]'
raise ValueError(msg.format(option_string, jobs))
jobs = min(jobs, multiprocessing.cpu_count())
spack.config.set('config:build_jobs', jobs, scope='command_line')
setattr(namespace, 'jobs', jobs)
@ -94,7 +96,8 @@ def __call__(self, parser, namespace, jobs, option_string):
def default(self):
# This default is coded as a property so that look-up
# of this value is done only on demand
return spack.config.get('config:build_jobs')
return min(spack.config.get('config:build_jobs'),
multiprocessing.cpu_count())
@default.setter
def default(self, value):

View file

@ -100,7 +100,7 @@
'verify_ssl': True,
'checksum': True,
'dirty': False,
'build_jobs': multiprocessing.cpu_count(),
'build_jobs': min(16, multiprocessing.cpu_count()),
}
}

View file

@ -20,14 +20,38 @@ def parser():
yield p
# Cleanup the command line scope if it was set during tests
if 'command_line' in spack.config.config.scopes:
spack.config.config.remove_scope('command_line')
spack.config.config.scopes['command_line'].clear()
@pytest.mark.parametrize('cli_args,expected', [
@pytest.fixture(params=[1, 2, 4, 8, 16, 32])
def ncores(monkeypatch, request):
"""Mocks having a machine with n cores for the purpose of
computing config:build_jobs.
"""
def _cpu_count():
return request.param
# Patch multiprocessing.cpu_count() to return the value we need
monkeypatch.setattr(multiprocessing, 'cpu_count', _cpu_count)
# Patch the configuration parts that have been cached already
monkeypatch.setitem(spack.config.config_defaults['config'],
'build_jobs', min(16, request.param))
monkeypatch.setitem(
spack.config.config.scopes, '_builtin',
spack.config.InternalConfigScope(
'_builtin', spack.config.config_defaults
))
return request.param
@pytest.mark.parametrize('cli_args,requested', [
(['-j', '24'], 24),
([], multiprocessing.cpu_count())
# Here we report the default if we have enough cores, as the cap
# on the available number of cores will be taken care of in the test
([], 16)
])
def test_setting_parallel_jobs(parser, cli_args, expected):
def test_setting_parallel_jobs(parser, cli_args, ncores, requested):
expected = min(requested, ncores)
namespace = parser.parse_args(cli_args)
assert namespace.jobs == expected
assert spack.config.get('config:build_jobs') == expected