Accelerare il processo di costruzione con le distutils

Sto programmando un’estensione C ++ per Python e sto usando distutils per compilare il progetto. Con la crescita del progetto, la ricostruzione richiede sempre più tempo. C’è un modo per accelerare il processo di costruzione?

Ho letto che le build parallele (come con make -j ) non sono possibili con le distutils. Ci sono buone alternative alle distutils che potrebbero essere più veloci?

Ho anche notato che sta ricompilando tutti i file object ogni volta che chiamo python setup.py build , anche quando ho cambiato solo un file sorgente. Dovrebbe essere così o potrei fare qualcosa di sbagliato qui?

Nel caso in cui aiuti, ecco alcuni dei file che provo a compilare: https://gist.github.com/2923577

Grazie!

  1. Prova a build con la variabile di ambiente CC="ccache gcc" , che velocizzerà la compilazione in modo significativo quando la sorgente non è cambiata. (stranamente, distutils usa CC anche per i file sorgente c ++). Installa il pacchetto ccache, ovviamente.

  2. Dato che hai una singola estensione che viene assemblata da più file di oggetti compilati , puoi distutils di patch scimmia per compilare quelli in parallelo (sono indipendenti) – inserisci questo nel tuo setup.py (regola N=2 come desideri):

     # 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 
  3. Per completezza, se disponi di più estensioni , puoi utilizzare la seguente soluzione:

     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 

Ho questo lavoro su Windows con clcache, derivato dalla risposta di 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 

Negli esempi limitati che hai fornito nel link, sembra abbastanza ovvio che hai qualche malinteso su quali siano alcune delle caratteristiche della lingua. Ad esempio, gsminterface.h ha un sacco di spazio dei nomi static , il che probabilmente non è voluto. Ogni unità di traduzione che include quell’intestazione compilerà la propria versione per tutti i simboli dichiarati nell’intestazione. Gli effetti collaterali di questo non sono solo il tempo di compilazione, ma anche il code bloat (binari più grandi) e il tempo di collegamento in quanto il linker deve elaborare tutti quei simboli.

Ci sono ancora molte domande che riguardano il processo di compilazione a cui non hai risposto, ad esempio se pulisci ogni volta prima di ricompilarlo. Se si sta facendo ciò, si potrebbe prendere in considerazione ccache , che è uno strumento che memorizza nella cache il risultato del processo di compilazione, in modo che se si esegue make clean; make target make clean; make target Solo il preprocessore verrà eseguito per qualsiasi unità di traduzione che non è stata modificata. Nota che finché continui a mantenere la maggior parte del codice nelle intestazioni, questo non offrirà molti vantaggi, poiché una modifica in un’intestazione modifica tutte le unità di traduzione che la includono. (Non conosco il tuo sistema di build, quindi non posso dirti se il python setup.py build verrà pulito o meno)

Il progetto non sembra grande altrimenti, quindi sarei sorpreso se ci volessero più di qualche secondo per compilare.