GAE が Python 3 に対応しました

今日、 GAE が Ruby と node.js をサポートするという発表がありましたが、実際のところ、今まであった Managed VM が GAE flexible environment と名前を変えたようです。

そして GAE flexible environment の公式イメージとして、 Ruby, node.js などに加えて、 Python 2.7, 3.4 もあります。 つまり、「GAE が node.js に対応した」と同じレベルで「GAE が Python 3 に対応」しました。 「Ruby や node.js に対応したのに Python 3 対応しないのかよ!」ということはありません。

とはいえ、昔からあるGAE (GAE standard environment) では、Python 2.7, Java, PHP, Go のサポートだけで、 GAE の大きな魅力だった無料枠が使えるのもこれらの言語だけです。

無料枠が提供できた理由は、インスタンスをゼロまでスケールダウンできる(リクエストが来てからインスタンスを立ち上げられる)からなので、今後 flexible environment の起動速度が改善されたらまた状況は変わるかもしれません。

最近出たPython本2冊紹介

1冊めは入門書。結城さんの Tweet を見て知った。 www.amazon.co.jp

プログラミング自体が初めてという人向けの入門書なので真面目に読む気は無いんだけど、応援のために買ってみた。

中身をかいつまんで読んでみたんだけど、例えばよくある間違いのコードを表示されるエラーメッセージまでセットで紹介していたりして、周りに聞ける人が居ない入門者がこの本を読みながら勉強するときに困ることを極力減らそうという親切さが伝わってきた。

入門書を読む機会が無いのでおすすめ本を聞かれても答えられなかったんだけど、これからはこの本をおすすめしようと思う。

2冊めは「入門書の次に読む本」(ただし、この場合の入門書はプログラミング非初心者向けの入門書だと思う)

www.amazon.co.jp

中上級者向けの本も最近増えて嬉しい限りだけど、この本は Effective Python よりは広く、 パーフェクト Python よりは狭い。 たとえば PHPC# 書いてた人が Python を始めるときに、Python オンラインドキュメントのチュートリアルを読んだあとにこの本を読めばいい感じのバランスになると思う。

miniconda3 が遅かった件

このブログで何度か紹介している TechEmpower FrameworkBenchmarks では、フレームワークごとにテストを Travis で回している。 Python をビルドするときには速度重視で make profile-opt でビルドしたいのだが、ビルド時間が長くなり、 Python 2 と 3 の両方をビルドすると Travisインスタンスタイムアウトして fail してしまう。

毎回同じ Python をビルドするのが無駄すぎるので、高速な Python をバイナリ配布して欲しい。最近はインテルPython が話題だが、多分まだ一般利用は無理だし、(高速化した)数学系のライブラリをバンドルしてるので Web 系にはちと向かない。

数学系のバンドルをした Python ディストリの一つとして Anaconda があり、その軽量版として miniconda があるので、それが高速なインタプリタを使ってるのではないかと期待して pybench してみた。

しかし、 pyenv で profile-opt なしでビルドした Python 3.5.1 が average: 2967ms の環境で miniconda は average: 3293ms と、かなり遅かった。 使っている GCC がかなり古いせいかな。

    Python:
       Implementation: CPython
       Executable:     /home/inada-n/local/miniconda3/bin/python
       Version:        3.5.1
       Compiler:       GCC 4.4.7 20120313 (Red Hat 4.4.7-1)
       Bits:           64bit
       Build:          Dec  7 2015 11:16:01 (#default)
       Unicode:        UCS4

ところで、 Mac の Homebrew では bottle でバイナリ配布ができているので、 PGO が使える Mac では profile-opt をデフォルトで指定してやれば、みんなが時間をかけて自前ビルドしなくても少し高速な Python を利用できるようになるはずだ。今度時間があるときに試していけそうなら pull request してみたい。

判りやすいやり方が1つは必要だ -- その1つだけなら最高だね

はじめてのにき(2016-03-10) を見て。

Python の Zen の一つ "There should be one - and preferably only one - obvious way to do it." はよく誤解される。

これはもちろん PerlTMTOWTDI 「やり方は1つじゃない」に対するものなのだけど、反対の「やり方は1つが良い」という意味では決して無い。 現実的なプログラミング言語でそんなアホな目標を持ってはいない。訳すなら「判りやすいやり方が1つは必要だ -- その1つだけなら最高だね」で、 preferably~ の部分は補助的なものだ。

例えば conditional expresson x if cond else y は、普通の if/else 文と一時変数を使えば良いので、新しいやり方だ。でも、これがない時代は、一部のエキスパートが cond and x or y というイディオムを使ってしまっていた。このイディオムは知らない人にはトリッキーで読みにくい。

最近だと、 Python 3.6 で "{foo} and {bar}".format(foo=foo, bar=bar)f"{foo} and {bar}" と書けるというのも、変数が多い時に読みにくい、format の引数に **locals() を使うという良くないトリックを使う誘惑が大きいなどといったことを理由にとうとう受け入れられた。

つまりこのZenは、新しいやり方を追加するのは今までのやり方よりも明らかに読みやすく判りやすいなどのメリットがあるときで、ちょっとタイプ数を減らすとか好みの書き方だということではやり方を増やさないということだ。

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)
このブログに乗せているコードは引用を除き CC0 1.0 で提供します。