Skip to content

Commit b9d0027

Browse files
authored
feat: parallel compiler (#2521)
1 parent 07b069a commit b9d0027

File tree

3 files changed

+131
-2
lines changed

3 files changed

+131
-2
lines changed

docs/compiling.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ that is supported via a ``build_ext`` command override; it will only affect
6868
ext_modules=ext_modules
6969
)
7070
71+
Since pybind11 does not require NumPy when building, a light-weight replacement
72+
for NumPy's parallel compilation distutils tool is included. Use it like this:
73+
74+
from pybind11.setup_helpers import ParallelCompile
75+
76+
# Optional multithreaded build
77+
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
78+
79+
setup(...
80+
81+
The argument is the name of an environment variable to control the number of
82+
threads, such as ``NPY_NUM_BUILD_JOBS`` (as used by NumPy), though you can set
83+
something different if you want. You can also pass ``default=N`` to set the
84+
default number of threads (0 will take the number of threads available) and
85+
``max=N``, the maximum number of threads; if you have a large extension you may
86+
want set this to a memory dependent number.
87+
7188
.. _setup_helpers-pep518:
7289

7390
PEP 518 requirements (Pip 10+ required)

pybind11/setup_helpers.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from distutils.extension import Extension as _Extension
5050

5151
import distutils.errors
52+
import distutils.ccompiler
5253

5354

5455
WIN = sys.platform.startswith("win32")
@@ -279,3 +280,108 @@ def build_extensions(self):
279280
# Python 2 doesn't allow super here, since distutils uses old-style
280281
# classes!
281282
_build_ext.build_extensions(self)
283+
284+
285+
# Optional parallel compile utility
286+
# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
287+
# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
288+
# and NumPy's parallel distutils module:
289+
# https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py
290+
class ParallelCompile(object):
291+
"""
292+
Make a parallel compile function. Inspired by
293+
numpy.distutils.ccompiler.CCompiler_compile and cppimport.
294+
295+
This takes several arguments that allow you to customize the compile
296+
function created:
297+
298+
envvar: Set an environment variable to control the compilation threads, like NPY_NUM_BUILD_JOBS
299+
default: 0 will automatically multithread, or 1 will only multithread if the envvar is set.
300+
max: The limit for automatic multithreading if non-zero
301+
302+
To use:
303+
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
304+
or:
305+
with ParallelCompile("NPY_NUM_BUILD_JOBS"):
306+
setup(...)
307+
"""
308+
309+
__slots__ = ("envvar", "default", "max", "old")
310+
311+
def __init__(self, envvar=None, default=0, max=0):
312+
self.envvar = envvar
313+
self.default = default
314+
self.max = max
315+
self.old = []
316+
317+
def function(self):
318+
"""
319+
Builds a function object usable as distutils.ccompiler.CCompiler.compile.
320+
"""
321+
322+
def compile_function(
323+
compiler,
324+
sources,
325+
output_dir=None,
326+
macros=None,
327+
include_dirs=None,
328+
debug=0,
329+
extra_preargs=None,
330+
extra_postargs=None,
331+
depends=None,
332+
):
333+
334+
# These lines are directly from distutils.ccompiler.CCompiler
335+
macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile(
336+
output_dir, macros, include_dirs, sources, depends, extra_postargs
337+
)
338+
cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs)
339+
340+
# The number of threads; start with default.
341+
threads = self.default
342+
343+
# Determine the number of compilation threads, unless set by an environment variable.
344+
if self.envvar is not None:
345+
threads = int(os.environ.get(self.envvar, self.default))
346+
347+
def _single_compile(obj):
348+
try:
349+
src, ext = build[obj]
350+
except KeyError:
351+
return
352+
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
353+
354+
try:
355+
import multiprocessing
356+
from multiprocessing.pool import ThreadPool
357+
except ImportError:
358+
threads = 1
359+
360+
if threads == 0:
361+
try:
362+
threads = multiprocessing.cpu_count()
363+
threads = self.max if self.max and self.max < threads else threads
364+
except NotImplementedError:
365+
threads = 1
366+
367+
if threads > 1:
368+
for _ in ThreadPool(threads).imap_unordered(_single_compile, objects):
369+
pass
370+
else:
371+
for ob in objects:
372+
_single_compile(ob)
373+
374+
return objects
375+
376+
return compile_function
377+
378+
def install(self):
379+
distutils.ccompiler.CCompiler.compile = self.function()
380+
return self
381+
382+
def __enter__(self):
383+
self.old.append(distutils.ccompiler.CCompiler.compile)
384+
return self.install()
385+
386+
def __exit__(self, *args):
387+
distutils.ccompiler.CCompiler.compile = self.old.pop()

tests/extra_setuptools/test_setuphelper.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
1111

1212

13+
@pytest.mark.parametrize("parallel", [False, True])
1314
@pytest.mark.parametrize("std", [11, 0])
14-
def test_simple_setup_py(monkeypatch, tmpdir, std):
15+
def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
1516
monkeypatch.chdir(tmpdir)
1617
monkeypatch.syspath_prepend(MAIN_DIR)
1718

@@ -39,13 +40,18 @@ def test_simple_setup_py(monkeypatch, tmpdir, std):
3940
cmdclass["build_ext"] = build_ext
4041
4142
43+
parallel = {parallel}
44+
if parallel:
45+
from pybind11.setup_helpers import ParallelCompile
46+
ParallelCompile().install()
47+
4248
setup(
4349
name="simple_setup_package",
4450
cmdclass=cmdclass,
4551
ext_modules=ext_modules,
4652
)
4753
"""
48-
).format(MAIN_DIR=MAIN_DIR, std=std),
54+
).format(MAIN_DIR=MAIN_DIR, std=std, parallel=parallel),
4955
encoding="ascii",
5056
)
5157

0 commit comments

Comments
 (0)