2012-06-13 26 views
19

私はPython用のC++拡張をプログラミングしています。私はdistutilsを使ってプロジェクトをコンパイルしています。プロジェクトが成長するにつれて、それを再構築するのにはより長い時間がかかります。ビルドプロセスをスピードアップする方法はありますか?distutilsを使ったビルドプロセスの高速化

distutilsでは並列ビルド(make -jと同じ)はできません。もっと速くなるかもしれないdistutilsの良い選択肢はありますか?

私はpython setup.py buildを呼び出す度に、すべてのオブジェクトファイルを再コンパイルしていることに気付きました。これが当てはまるか、私はここで何か間違っているかもしれませんか?

それはここで、助け場合は、私がコンパイルしようとするファイルのいくつかは以下のとおりです。https://gist.github.com/2923577

ありがとう!

+1

どのようにビルドプロセスがありますか?あなたはいつも清掃/再建していますか?あなたのコード(特にヘッダー)はどうですか?前方宣言を使用していますか?あなたの環境は何ですか?プリコンパイルされたヘッダーを使用できますか? –

+0

私はすべての質問に答える方法を知らなかったので、ソースコードの一部にリンクを追加しました。私は 'python setup 'を使ってプロジェクトをビルドしています。pyビルド '、より良い方法やより良いコマンドがありますか?環境はLinuxとMacです。 – Lucas

+1

すべてのオブジェクトファイルを再コンパイルすることが予想されます。エクステンションの余分なオプションは、.cファイルを変更せずに出力を変更することがあります。 http://bugs.python.org/issue5372 –

答えて

24
  1. ソースが変更されていないとき大幅にビルドをスピードアップすることが、環境変数CC="ccache gcc"でビルドしてみます。 (間違いなく、distutilsはCCをC++ソースファイルにも使用しています)。もちろん、ccacheパッケージをインストールしてください。

  2. 複数のコンパイル済みオブジェクトファイルからアセンブルされた単一の拡張子があるので、distutilsをmonkey-patchして並列にコンパイルすることができます(独立しています) - これを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 
    
+0

サルのパッチは素晴らしい作品です!ありがとう。 – Lucas

+1

猿パッチ! +1。デバッグ時に役立つように、 'ThreadPool'の終了/結合後にのみ' CompileError'をキャッチするために、 'imap()'反復の周りにtryブロックを追加しました。このようにして、出力の最後に失敗した正確なコンパイルコマンドを簡単に見ることができます。ただし、プロセスを孤立させることはありません。 –

+1

誰かが窓に猿のパッチで成功していますか?私はそれを試しましたが、それはオブジェクトを構築することをスキップし、リンクステップにまっすぐにジャンプするように見えます! – Nick

1

リンクで提供している限定的な例では、言語の機能のいくつかを誤解していると思われます。たとえば、gsminterface.hには、おそらく意図しないネームスペースレベルstaticがたくさんあります。そのヘッダーを含むすべての翻訳単位は、そのヘッダーで宣言されたすべてのシンボルのための独自のバージョンをコンパイルします。この副作用はコンパイル時だけでなく、リンカがすべてのシンボルを処理する必要があるため、コードが肥大化し(大きなバイナリ)、リンク時になります。

ビルドプロセスに影響を与える質問にはまだ答えがありません。たとえば、再コンパイルするたびにいつもきれいにするかどうかなどです。その場合、ccacheを使用すると、のキャッシュにはという結果が得られます。したがって、make clean; make targetを実行すると、変更されていないすべての翻訳単位に対してプリプロセッサが実行されます。ヘッダーのほとんどのコードを維持し続ける限り、ヘッダーの変更によってヘッダーが含まれるすべての翻訳単位が変更されるため、多くのメリットはありません。 (私はあなたのビルドシステムを知らないので、私はpython setup.py buildますクリーンかどうかを伝えることはできません)プロジェクトは大そうは思えない

、それが数秒以上かかった場合、私は驚くだろうコンパイル。

+0

私はhttp://docs.python.org/extending/extending.htmlのチュートリアルとAPIドキュメントに従っています。これは 'static'をたくさん使っています。私はビルドを明示的にクリーンアップしていません(つまり 'python setup.py clean'を呼び出さない)、' python setup.py build'を呼び出すだけで、再構築の前にクリーンを実行するようには見えません。わからない*何をしているのか。そして、あなたが正しいです、ビルドは約40秒かかります。しかし、それが10秒しかかからなければ、私はもっと幸せだろう。 – Lucas

+1

@ルーカス:このチュートリアルでは、ヘッダーではなく、単一の翻訳単位で定義された関数を参照している可能性があります。ヘッダー内の名前空間レベルの '静的'シンボルは意味をなさない。関数を 'static'なしで宣言し、それらを単一の翻訳単位で定義するだけです(もう一度' static'はありません)。これにより、実装を変更するときに再コンパイルする翻訳単位の数が減り、ヘッダーも変更されたときに再コンパイルするコストが削減されます。 –

+0

@dribeas:ありがとう、私はコードを再構成しました(https://github.com/lucastheis/cisa/tree/0218a8fe7f46c70e43ca41158164585accfe9a71/code/isa)。再コンパイルしてください)、私はコードが今よりずっと意味をなさないと思います。 – Lucas

5

私はeudoxosの答えに由来し、clcacheとWindows上でこの作業を持っている:

# 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