Python をビルドするときに LTO (Link Time Optimization) を使う

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

prompt_toolkit がアツい

とりあえず mycliaws-shell のスクリーンキャストを見てください。

f:id:methane:20160302004342g:plain f:id:methane:20160302004350g:plain

prompt_toolkit はこのようなリッチコンソールアプリを作るためのライブラリです。 Windows でも動きます。

Jupyter (ipython notebook) を切り離した、コンソール版の ipython も次のメジャーバージョンでは readline ベースから prompt_toolkit ベースに作りなおされています。 ipython 以外にも ptpython というシェルもあり、 ipython の各種 magic が不要な場合はこちらで十分でしょう。

https://github.com/jonathanslenders/python-prompt-toolkit#projects-using-prompt-toolkit には、他にも prompt_toolkit を採用したツールが紹介されています。

VSCode や Atom のような現代的なエディタが登場しても、コンソールの中で生活する時間が長いオールドタイプにとって、このライブラリを使った高機能なコンソールアプリが増えてくれるのはすごく胸熱です。

TechEmpower FrameworkBenchmarks Round 12

普段のラウンドは試験的にベンチマーク回して preview という形でMLに公開し、エラーや設定ミスを修正するサイクルを数回繰り返すんだけど、今回はベンチマークに使ってた PEAK Hosting 上のマシンが退役するということで、1回目の preview だったはずのベンチマーク結果がそのまま Round 12 になり、 Round 13 で仕切り直しになる模様。

個人的な今回の Round 12 の目玉は Go の fasthttp. 標準の net/http に比べて、設計の綺麗さという面では改悪し、API互換性もなくなってるけど、よりアグレッシブにメモリのアロケートを避けられるので速い。

Go 1.5, 1.6 では GC の停止時間が改善されたけど、 1.7 ではスループットの改善がされる予定なので、 Round 13 が Go 1.7 の後になるなら、 net/http がどこまで fasthttp との差を詰められるかが見もの。

あとは次回に PyPy3 と uWSGI+PyPy 構成を投入できると良いなぁ。

「Go による Web アプリケーション開発」書評

Go による Web アプリケーション開発 を読みました。 (まだ後半はパラパラめくっただけですが)

この本は Go のチュートリアルでは無いです。 A Tour of Go とか The Go Programming Language で基本的な文法などは抑えた状態で読むべきでしょう。

この本で最初のサンプルアプリケーションは、 WebSocket によるチャットです。まず単に動くだけのチャットを作ってから、 Github などと OAuth2 で認証したり、各所からアイコンを引っ張ってきたりと、 Go で小さめの Web アプリを作るときに実用されそうな要素が並んでいます。

後半のサンプルアプリケーション (Twitter と連携する voting アプリ)では MongoDB や NSQ を使いはじめるので、ちょっとそのミドルウェアを使わない人にとっての実用性が落ちてきます。しかしこれも、 LAMP とは違った構成でとりあえず動くものを作る上では参考になると思います。

課題の選び方にはとてもセンスが光っている一方、気になるのはコード等実装レベルでのセンスです。 early return イディオムを使ってない(これはGo以外でも可読性の高いイディオムですが、特にGoらしいプログラムでは必須です)とか、 エラー処理が抜けているとか、最初のサンプルアプリでユーザーが自分でアイコンをアップロードする場面でファイルのパーミッションを 0777 で保存していたりとか、 コードレベルでもそれ以外の部分でもたくさんの顔をしかめるポイントがありました。

しかし、気になったポイントのほとんどに鵜飼さんの「監訳注」が入って、細かくフォローされています。 さらには付録B「Goらしいコードの書き方」で、本文中のGoらしくないコードをGoらしくリファクタリングしていきます。 本文の翻訳の方も詰まらずにすらすら読めるものですし、原文よりも絶対に邦訳の方がオススメです。

例えばWeb企業で新人研修で1周間くらいでGoを教える場合には、 Tour of Go とこの本の最初のサンプルプログラムまでをセットにするのが良いと思います。

Python を速くする取り組み

速い Python 実装といえば PyPy が有名ですが、 Python 3 へのキャッチアップが遅い、 CPython が持っている Python/C API のサポートがまだ弱く遅い、などの欠点があります。

また、 Google の1年プロジェクトだった Unladen Swallow もありました。これは CPython をフォークして LLVMJIT を実装するものでした。この fork 実装は終わりましたが、この時期まだ不安定だったLLVMへの貢献は大きく、(ちゃんとおってないので憶測ですが)現代LLVMを利用したJITを実装しているプロジェクトは全部間接的に Unladen Swallow の成果の上に成り立っていると言えるかもしれません。

終了した JIT プロジェクトといえば、 psyco もありました。これはベタに CPython の JIT を実装していましたが、メンテが大変なのと PyPy に性能で追いぬかれたので終了しました。

他の速度を目的とした Python 実装としては Dropbox の Pyston があります。これも PyPy と同じく Python 2 がメインターゲットですし CPython との互換性も低いですが、より保守的な JIT を実装しています。最近 Nexedi が Pyston の開発に加わりました

また、 MicroPython という組み込み向けの Python もあります。これは CRuby と mruby の関係に似ていると思います。 CPython のしがらみに囚われずにいろいろな動的言語の高速化手法を取り込めるため、 CPython より速いのですが、PC上で普段使いとして使うにはライブラリのカバーしてる範囲が狭いのが欠点です。こっちは pyston と違って Python 3 の実装です。

最近活発な CPython を高速化するプロジェクトが2つあります。 FAT PythonMicrosoftPyjion です。

FAT Python は CPython を高速化にしようとするプロジェクトです。 Ruby 2 系で行われているようなメソッドキャッシュの改良が試みられていたり、さらにアグレッシブに、 AST レベルでのインライン展開や定数の畳み込みといった試みをプラガブルにしようとしています。もちろん Python 3 ベースです。

Pyjion も CPython ベースで、 JIT エンジンを CPython に付けられる用にする、その JIT の参照実装というか実効性の検証用に、 CoreCLR を使ったJITを組み込もうというものです。 IronPython と違って Python そのものの .NET ベースの実装というわけではなくてあくまでも CPython の拡張を提案するプロジェクトです。 まだ詳細は発表されていませんが、もうすぐ PyCon で発表があるはずです。

個人的には、活発化してきた PyPy3 の開発が進んでくれることと、 FAT Python の成果の一部が Python 3.6 に間に合うことを期待しています。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。