タイムラインで Ruby が LTO で速くなるという話題を目にしたので、 Python でどう使うんだったっけ?と思って調べてみました。
ちょうど configure オプションに --with-lto を付けるパッチが投稿されていました。
Issue 25702: Link Time Optimizations support for GCC and CLANG - Python tracker
このパッチだと PGO (Profile Guided Optimization) と LTO を併用することしかできないようで、 PGO 使うと1割位速くなるものの、プロファイル取得のために Python のテストケース舐めるのでビルドにすごい時間がかかってしまいます。 PGO なしで LTO だけ使わせてってコメントしておきました。
さて、このパッチを当てれば PGO+LTO ができるはずですが、このパッチがやってるのは所詮 CFLAGS と LDFLAGS を設定しているだけなので、パッチ当てない状態で Python 3.5.1 を LTO ビルドしてみます。
# clang の場合は LTOFLAGS='-flto' だけで良いはず $ LTOFLAGS='-flto -fuse-linker-plugin -ffat-lto-objects -flto-partition=none' $ CFLAGS=$LTOFLAGS LDFLAGS=$LTOFLAGS ./configure --prefix=... $ make -j32
これで Debian 8.3 jessie ならビルドに成功しましたが、 Ubuntu 14.04 trusty では失敗してしまいました。
16746 – ld complains about .gnu.warning symbols referenced by linker plugin inputs
このバグを踏んでいそうなのですが、 LTO が使えないとあちこちから悲鳴あがると思うので、 Ubuntu で LTO 使う正しいオプションがあればどなたか教えてください。
さて、パフォーマンスですが、 PGO+LTO のパフォーマンスは上記の issue を参照してもらうとして、 何もなし (-O3) と LTO のパフォーマンスを pybench で比較してみます。
inada-n@bench:~/work/Python-3.5.1$ ./python Tools/pybench/pybench.py -s lto.pybench -c default.pybench ------------------------------------------------------------------------------- PYBENCH 2.1 ------------------------------------------------------------------------------- * using CPython 3.5.1 (default, Mar 3 2016, 09:47:49) [GCC 4.9.2] * disabled garbage collection * system check interval set to maximum: 2147483647 * using timer: time.perf_counter * timer: resolution=1e-09, implementation=clock_gettime(CLOCK_MONOTONIC) ------------------------------------------------------------------------------- Benchmark: lto.pybench ------------------------------------------------------------------------------- Rounds: 10 Warp: 10 Timer: time.perf_counter Machine Details: Platform ID: Linux-3.16.0-4-amd64-x86_64-with-debian-8.3 Processor: Python: Implementation: CPython Executable: /home/inada-n/work/Python-3.5.1/python Version: 3.5.1 Compiler: GCC 4.9.2 Bits: 64bit Build: Mar 3 2016 09:38:51 (#default) Unicode: UCS4 ------------------------------------------------------------------------------- Comparing with: default.pybench ------------------------------------------------------------------------------- Rounds: 10 Warp: 10 Timer: time.perf_counter Machine Details: Platform ID: Linux-3.16.0-4-amd64-x86_64-with-debian-8.3 Processor: Python: Implementation: CPython Executable: /home/inada-n/work/Python-3.5.1/python Version: 3.5.1 Compiler: GCC 4.9.2 Bits: 64bit Build: Mar 3 2016 09:47:49 (#default) Unicode: UCS4 Test minimum run-time average run-time this other diff this other diff ------------------------------------------------------------------------------- BuiltinFunctionCalls: 47ms 50ms -6.6% 48ms 51ms -6.0% BuiltinMethodLookup: 29ms 29ms -1.3% 29ms 29ms -0.1% CompareFloats: 32ms 33ms -2.8% 34ms 34ms -0.5% CompareFloatsIntegers: 67ms 70ms -3.9% 69ms 71ms -3.1% CompareIntegers: 48ms 46ms +5.1% 49ms 47ms +5.8% CompareInternedStrings: 30ms 31ms -1.9% 31ms 31ms -1.6% CompareLongs: 28ms 26ms +8.0% 29ms 27ms +8.5% CompareStrings: 26ms 26ms -0.9% 27ms 26ms +1.5% ComplexPythonFunctionCalls: 47ms 51ms -8.9% 48ms 52ms -7.8% ConcatStrings: 32ms 33ms -3.2% 33ms 34ms -2.2% CreateInstances: 51ms 52ms -2.5% 52ms 53ms -3.5% CreateNewInstances: 38ms 40ms -4.5% 39ms 41ms -4.4% CreateStringsWithConcat: 68ms 69ms -1.4% 70ms 71ms -0.4% DictCreation: 53ms 51ms +5.2% 55ms 52ms +6.7% DictWithFloatKeys: 41ms 42ms -2.2% 43ms 43ms -0.0% DictWithIntegerKeys: 34ms 34ms +0.1% 35ms 35ms +0.5% DictWithStringKeys: 31ms 32ms -1.3% 32ms 32ms -1.6% ForLoops: 26ms 30ms -12.1% 28ms 30ms -8.7% IfThenElse: 42ms 41ms +2.6% 43ms 41ms +5.0% ListSlicing: 40ms 40ms -0.8% 41ms 41ms -0.4% NestedForLoops: 42ms 42ms -0.3% 43ms 43ms +0.6% NestedListComprehensions: 42ms 47ms -11.9% 45ms 50ms -10.5% NormalClassAttribute: 89ms 96ms -7.9% 92ms 98ms -5.9% NormalInstanceAttribute: 47ms 45ms +4.8% 48ms 45ms +4.9% PythonFunctionCalls: 41ms 44ms -7.5% 41ms 45ms -7.4% PythonMethodCalls: 53ms 59ms -9.4% 55ms 60ms -8.5% Recursion: 69ms 73ms -5.1% 71ms 74ms -4.2% SecondImport: 36ms 41ms -12.0% 38ms 42ms -9.9% SecondPackageImport: 45ms 42ms +6.5% 46ms 43ms +7.0% SecondSubmoduleImport: 115ms 107ms +7.9% 117ms 108ms +7.9% SimpleComplexArithmetic: 27ms 29ms -6.5% 28ms 30ms -4.5% SimpleDictManipulation: 60ms 65ms -7.8% 61ms 66ms -7.0% SimpleFloatArithmetic: 33ms 30ms +7.4% 34ms 31ms +8.3% SimpleIntFloatArithmetic: 36ms 38ms -3.3% 37ms 38ms -4.0% SimpleIntegerArithmetic: 36ms 38ms -5.2% 37ms 38ms -4.1% SimpleListComprehensions: 36ms 37ms -3.2% 38ms 41ms -7.5% SimpleListManipulation: 34ms 34ms -1.3% 35ms 38ms -6.8% SimpleLongArithmetic: 26ms 26ms +0.3% 27ms 30ms -7.5% SmallLists: 45ms 47ms -4.1% 46ms 56ms -17.2% SmallTuples: 51ms 54ms -6.3% 53ms 62ms -14.8% SpecialClassAttribute: 92ms 97ms -5.0% 95ms 99ms -4.8% SpecialInstanceAttribute: 46ms 45ms +2.5% 48ms 46ms +3.9% StringMappings: 71ms 100ms -29.0% 73ms 101ms -27.8% StringPredicates: 49ms 59ms -17.8% 50ms 60ms -16.5% StringSlicing: 48ms 47ms +3.3% 79ms 47ms +66.2% TryExcept: 24ms 29ms -16.9% 25ms 30ms -15.8% TryFinally: 35ms 37ms -6.0% 36ms 38ms -4.6% TryRaiseExcept: 12ms 13ms -7.5% 13ms 14ms -7.2% TupleSlicing: 48ms 50ms -2.9% 49ms 51ms -2.7% WithFinally: 52ms 57ms -8.4% 53ms 58ms -8.2% WithRaiseExcept: 42ms 46ms -8.8% 43ms 47ms -9.1% ------------------------------------------------------------------------------- Totals: 2291ms 2398ms -4.5% 2390ms 2470ms -3.2% (this=lto.pybench, other=default.pybench)