タイムラインで 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)