2012-06-13 19 views
19

Programuję rozszerzenie C++ dla Pythona i używam distutils do kompilacji projektu. Wraz z rozwojem projektu przebudowa trwa dłużej i dłużej. Czy istnieje sposób na przyspieszenie procesu budowania?Przyspieszenie procesu kompilacji z distutils

Czytałem, że równoległe kompilacje (jak w przypadku make -j) nie są możliwe w przypadku distutils. Czy istnieją dobre alternatywy dla distutils, które mogą być szybsze?

Zauważyłem również, że jest to ponowna kompilacja wszystkich plików obiektów za każdym razem, gdy dzwonię pod numer python setup.py build, nawet gdy zmieniłem tylko jeden plik źródłowy. Czy to możliwe, czy też robię coś nie tak?

W przypadku pomaga, oto niektóre pliki, które próbuję skompilować: https://gist.github.com/2923577

Dzięki!

+1

Jak to proces kompilacji? Czy zawsze czyścisz/odbudowujesz? Jaki jest twój kod (szczególnie nagłówki)? Czy używasz deklaracji forward? Jakie jest twoje środowisko? Czy możesz używać prekompilowanych nagłówków? –

+0

Nie wiedziałem, jak odpowiedzieć na wszystkie pytania, więc dodałem link do części kodu źródłowego. Buduję projekt za pomocą 'konfiguracji pythona.py build ", czy istnieje lepszy czy lepszy sposób polecenia? Środowiska to Linux i Mac. – Lucas

+1

Ponowna kompilacja wszystkich plików obiektów jest oczekiwana: Rozszerzenie dodatkowych opcji może zmienić wyjście bez zmiany pliku .c. http://bugs.python.org/issue5372 –

Odpowiedz

24
  1. Spróbuj budynek z zmiennej środowiskowej CC="ccache gcc", że przyspieszy build znacząco, gdy źródło nie uległo zmianie. (Dziwnie, distutils używa CC również dla plików źródłowych C++). Oczywiście zainstaluj pakiet ccache.

  2. Skoro masz jedno rozszerzenie, które jest zbudowane z wielu skompilowanych plików obiektowych, można distutils małpa-Patch do kompilacji tych równolegle (są niezależne) - umieścić to w swoim setup.py (wyregulować N=2 jak ty Życzymy):

    # 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 
    
+0

Łata małpa działa świetnie! Dzięki. – Lucas

+1

Świetna łatka dla małp! +1. Aby pomóc w debugowaniu, dodałem również blok try wokół iteracji 'imap()', aby złapać 'CompileError', tylko podnosząc je po zakończeniu/dołączeniu do' ThreadPool'. W ten sposób nadal mogę łatwo zobaczyć dokładne polecenie kompilacji, które zawiodło u dołu wyjścia, nie osierocając żadnych procesów. –

+1

Czy ktokolwiek odniósł sukces z łatką małpy w oknach? Próbowałem, ale wygląda na to, że przeskakuje, budując obiekty i przeskakuje prosto do etapu łączenia! – Nick

1

W ograniczonych przykładach podanych w linku wydaje się dość oczywiste, że masz pewne nieporozumienia co do niektórych cech tego języka. Na przykład gsminterface.h ma wiele poziomów przestrzeni nazw static s, co prawdopodobnie jest niezamierzone. Każda jednostka tłumaczeniowa, która zawiera ten nagłówek, skompiluje własną wersję dla wszystkich symboli zadeklarowanych w tym nagłówku. Efektami ubocznymi tego są nie tylko czas kompilacji, ale także nadpisanie kodu (większe pliki binarne) i czas połączenia, ponieważ łącznik musi przetworzyć wszystkie te symbole.

Istnieje wciąż wiele pytań, które wpływają na proces kompilacji, na które nie udzielono odpowiedzi, na przykład czy czyścisz za każdym razem, zanim dokonasz rekompilacji. Jeśli robisz to, możesz chcieć rozważyć ccache, które jest narzędziem, które buforuje wynik procesu kompilacji, więc jeśli uruchomisz make clean; make target tylko preprocesor zostanie uruchomiony dla dowolnej jednostki tłumaczeniowej, która się nie zmieniła. Zauważ, że tak długo, jak utrzymujesz większość kodu w nagłówkach, nie będzie to miało dużej przewagi, ponieważ zmiana w nagłówku modyfikuje wszystkie jednostki tłumaczeniowe, które go zawierają. (Nie wiem system kompilacji, więc nie mogę powiedzieć, czy python setup.py build będzie czystej lub nie)

Projekt nie wydaje się duża inaczej, więc byłbym zaskoczony, gdyby to trwało dłużej niż kilka sekund do skompilować.

+0

Śledziłem samouczek i dokumentację API na http://docs.python.org/extending/extending.html, która również bardzo często używa 'static'. Nie czyszczę jawnie kompilacji (tzn. Nie nazywam 'python setup.py clean') i nie wydaje się, żeby po prostu wywołanie' python setup.py build' wykonało funkcję clean przed przebudowaniem, chociaż jestem nie wiem * co * robi. I masz rację, budowa zajmuje tylko około 40 lat. Ale jeśli zajęłoby to tylko 10 sekund, byłbym szczęśliwszy. – Lucas

+1

@Lucas: samouczek może odnosić się do funkcji zdefiniowanych w pojedynczej jednostce tłumaczeniowej, a nie do nagłówka. Poziom symboli "statyczne" w nagłówku nie ma sensu. Po prostu zadeklaruj funkcję (bez 'static') i zdefiniuj je w jednej jednostce tłumaczeniowej (znowu nie ma' static'). Zmniejszy to liczbę jednostek tłumaczeniowych do rekompilacji, gdy zmienisz tylko implementacje, oraz koszt rekompilacji, gdy zmieni się również nagłówek. –

+0

@dribeas: Dzięki, ja [zreorganizowałem kod] (https://github.com/lucastheis/cisa/tree/0218a8fe7f46c70e43ca41158164585accfe9a71/code/isa) i chociaż nie przyspieszyło to procesu budowania (wszystkie jednostki tłumaczeniowe wciąż są zawsze się skompilować), myślę, że kod ma teraz więcej sensu. – Lucas

5

mam tej pracy w systemie Windows z clcache, pochodzące z odpowiedzią eudoxos męska:

# 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