Ускорение процесса сборки с помощью distutils
я программирую расширение C++ для Python, и я использую distutils для компиляции проекта. По мере роста проекта восстановление занимает все больше времени. Есть ли способ ускорить процесс сборки?
Я читал, что параллельные сборки (как с make -j
) невозможно с distutils. Есть ли хорошие альтернативы distutils, которые могут быть быстрее?
Я также заметил, что он перекомпилирует все объектные файлы каждый раз, когда я вызываю python setup.py build
, даже когда я только изменен один исходный файл. Должно ли это быть так, или я могу сделать что-то неправильно здесь?
если это поможет, вот некоторые из файлов, которые я пытаюсь скомпилировать:https://gist.github.com/2923577
спасибо!
3 ответов
попробуйте построить с переменной среды
CC="ccache gcc"
, это значительно ускорит сборку, когда источник не изменится. (странно, distutils используетCC
также для исходных файлов c++). Установите пакет ccache, конечно.-
так как у вас есть один расширением состоящая из несколько скомпилированных объектных файлов, вы можете обезьяна-патч distutils компилировать их параллельно (они независимы) - put это setup.py (отрегулируйте
N=2
как хотите):# monkey-patch for parallel compilation def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None): # those lines are copied from distutils.ccompiler.CCompiler directly macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros, include_dirs, sources, depends, extra_postargs) cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) # parallel code N=2 # number of parallel compilations import multiprocessing.pool def _single_compile(obj): try: src, ext = build[obj] except KeyError: return self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # convert to list, imap is evaluated on-demand list(multiprocessing.pool.ThreadPool(N).imap(_single_compile,objects)) return objects import distutils.ccompiler distutils.ccompiler.CCompiler.compile=parallelCCompile
-
для полноты, если у вас есть несколько расширений, вы можете использовать следующее решение:
import os import multiprocessing try: from concurrent.futures import ThreadPoolExecutor as Pool except ImportError: from multiprocessing.pool import ThreadPool as LegacyPool # To ensure the with statement works. Required for some older 2.7.x releases class Pool(LegacyPool): def __enter__(self): return self def __exit__(self, *args): self.close() self.join() def build_extensions(self): """Function to monkey-patch distutils.command.build_ext.build_ext.build_extensions """ self.check_extensions_list(self.extensions) try: num_jobs = os.cpu_count() except AttributeError: num_jobs = multiprocessing.cpu_count() with Pool(num_jobs) as pool: pool.map(self.build_extension, self.extensions) def compile( self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None, ): """Function to monkey-patch distutils.ccompiler.CCompiler""" macros, objects, extra_postargs, pp_opts, build = self._setup_compile( output_dir, macros, include_dirs, sources, depends, extra_postargs ) cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) for obj in objects: try: src, ext = build[obj] except KeyError: continue self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # Return *all* object filenames, not just the ones we just built. return objects from distutils.ccompiler import CCompiler from distutils.command.build_ext import build_ext build_ext.build_extensions = build_extensions CCompiler.compile = compile
У меня есть эта работа над Windows с clcache, полученная из ответа eudoxos:
# Python modules
import datetime
import distutils
import distutils.ccompiler
import distutils.sysconfig
import multiprocessing
import multiprocessing.pool
import os
import sys
from distutils.core import setup
from distutils.core import Extension
from distutils.errors import CompileError
from distutils.errors import DistutilsExecError
now = datetime.datetime.now
ON_LINUX = "linux" in sys.platform
N_JOBS = 4
#------------------------------------------------------------------------------
# Enable ccache to speed up builds
if ON_LINUX:
os.environ['CC'] = 'ccache gcc'
# Windows
else:
# Using clcache.exe, see: https://github.com/frerich/clcache
# Insert path to clcache.exe into the path.
prefix = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(prefix, "bin")
print "Adding %s to the system path." % path
os.environ['PATH'] = '%s;%s' % (path, os.environ['PATH'])
clcache_exe = os.path.join(path, "clcache.exe")
#------------------------------------------------------------------------------
# Parallel Compile
#
# Reference:
#
# http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
#
def linux_parallel_cpp_compile(
self,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
depends=None):
# Copied from distutils.ccompiler.CCompiler
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
output_dir, macros, include_dirs, sources, depends, extra_postargs)
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
def _single_compile(obj):
try:
src, ext = build[obj]
except KeyError:
return
self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
# convert to list, imap is evaluated on-demand
list(multiprocessing.pool.ThreadPool(N_JOBS).imap(
_single_compile, objects))
return objects
def windows_parallel_cpp_compile(
self,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
depends=None):
# Copied from distutils.msvc9compiler.MSVCCompiler
if not self.initialized:
self.initialize()
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
output_dir, macros, include_dirs, sources, depends, extra_postargs)
compile_opts = extra_preargs or []
compile_opts.append('/c')
if debug:
compile_opts.extend(self.compile_options_debug)
else:
compile_opts.extend(self.compile_options)
def _single_compile(obj):
try:
src, ext = build[obj]
except KeyError:
return
input_opt = "/Tp" + src
output_opt = "/Fo" + obj
try:
self.spawn(
[clcache_exe]
+ compile_opts
+ pp_opts
+ [input_opt, output_opt]
+ extra_postargs)
except DistutilsExecError, msg:
raise CompileError(msg)
# convert to list, imap is evaluated on-demand
list(multiprocessing.pool.ThreadPool(N_JOBS).imap(
_single_compile, objects))
return objects
#------------------------------------------------------------------------------
# Only enable parallel compile on 2.7 Python
if sys.version_info[1] == 7:
if ON_LINUX:
distutils.ccompiler.CCompiler.compile = linux_parallel_cpp_compile
else:
import distutils.msvccompiler
import distutils.msvc9compiler
distutils.msvccompiler.MSVCCompiler.compile = windows_parallel_cpp_compile
distutils.msvc9compiler.MSVCCompiler.compile = windows_parallel_cpp_compile
# ... call setup() as usual
в ограниченных примерах, которые вы предоставили в ссылке, кажется довольно очевидным, что у вас есть некоторое недопонимание о том, что некоторые из особенностей языка. Например,gsminterface.h
имеет много уровней пространства имен static
s, что, вероятно, непреднамеренно. Каждая единица перевода, включающая этот заголовок, будет компилировать свою собственную версию для всех символов, объявленных в этом заголовке. Побочными эффектами этого являются не только время компиляции, но и раздувание кода (большие двоичные файлы) и время ссылки как компоновщик должен обработать все эти символы.
есть еще много вопросов, которые влияют на процесс сборки, на которые вы не ответили, например, очищаете ли вы каждый раз перед перекомпиляцией. Если вы делаете это, то вы можете рассмотреть ccache
, которая кэш результат процесса сборки, так что если вы запустите make clean; make target
только препроцессор будет запущен для любой единицы перевода, которая не изменилась. Заметьте, что пока поскольку вы поддерживаете большую часть кода в заголовках, это не даст большого преимущества, так как изменение заголовка изменяет все единицы перевода, которые его включают. (Я не знаю вашу систему сборки, поэтому я не могу сказать вам,python setup.py build
будет очистить или нет)
в противном случае проект не кажется большим, поэтому я был бы удивлен, если бы компиляция заняла больше нескольких секунд.