Accélérer le processus de construction avec distutils

Je suis en train de programmer une extension C++ pour Python et j'utilise distutils pour compiler le projet. Au fur et à mesure que le projet grandit, sa reconstruction prend de plus en plus de temps. Est-il un moyen d'accélérer le processus de construction?

J'ai lu que des constructions parallèles (comme avec make -j) ne sont pas possibles avec distutils. Existe-t-il de bonnes alternatives aux distutils qui pourraient être plus rapides?

J'ai également remarqué qu'il recompile tous les fichiers objet chaque fois que j'appelle python setup.py build, même lorsque je n'ai changé qu'un seul fichier source. Cela devrait - il être le cas ou Pourrais-je faire quelque chose de mal ici?

Au cas où cela aiderait, voici quelques-uns des fichiers que j'essaie de compiler: https://gist.github.com/2923577

Merci!

24
demandé sur Lucas 2012-06-13 15:25:21

3 réponses

  1. Essayez de construire avec la variable d'environnement CC="ccache gcc", ce qui accélérera considérablement la construction lorsque la source n'a pas changé. (étrangement, distutils utilise CC aussi pour les fichiers source C++). Installez le paquet ccache, bien sûr.

  2. Puisque vous avez une extension unique qui est assemblée à partir de plusieurs fichiers d'objets compilés , Vous pouvez monkey-patch distutils pour les compiler en parallèle ( ils sont indépendants) - mettez ceci dans votre setup.py (ajuster le N=2 comme vous le souhaitez):

    # 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. , Par souci d'exhaustivité, si vous avez plusieurs extensions, vous pouvez utiliser la solution suivante:

    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
    
28
répondu eudoxos 2018-08-24 18:49:30

Cela fonctionne sur Windows avec clcache, dérivé de la réponse d'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
7
répondu Nick 2013-09-24 21:44:36

Dans les exemples limités que vous avez fournis dans le lien, il semble assez évident que vous avez un malentendu sur certaines des caractéristiques de la langue. Par exemple, le gsminterface.h a beaucoup de niveau d'espace de noms statics, ce qui est probablement involontaire. Chaque unité de traduction qui inclut cet en-tête compilera sa propre version pour chacun des symboles déclarés dans cet en-tête. Les effets secondaires de ceci ne sont pas seulement le temps de compilation mais aussi le gonflement du code (binaires plus grands) et le temps de liaison comme l'éditeur de liens doit traiter tous ces symboles.

Il y a encore beaucoup de questions qui affectent le processus de construction auxquelles vous n'avez pas répondu, par exemple, si vous nettoyez chaque fois avant de recompiler. Si vous faites cela, vous voudrez peut-être considérer ccache, qui est un outil qui met en cache le résultat du processus de construction, de sorte que si vous exécutez make clean; make target seul le préprocesseur sera exécuté pour toute unité de traduction qui n'a pas changé. Notez que tant que vous continuez à maintenir la plupart du code dans les en-têtes, cela n'offrira pas beaucoup d'avantage, car un changement dans un en-tête modifie toutes les unités de traduction qui l'incluent. (Je ne connais pas votre système de construction, donc je ne peux pas vous dire si {[4] } nettoiera ou non)

Le projet ne semble pas grand autrement, donc je serais surpris si cela prenait plus de quelques secondes pour compiler.

1
répondu David Rodríguez - dribeas 2012-06-13 12:02:04