2016-05-21 2 views
26

私はPython GithubリポジトリからPython 3.6 alphaビルドをダウンロードしました。私の好きな新機能の1つはリテラル文字列の書式設定です。それはそうと同じように使用することができます。なぜPython 3.6アルファでリテラルフォーマットの文字列が遅いのですか? (現在stable 3.6で修正されています)

>>> x = 2 
>>> f"x is {x}" 
"x is 2" 

これはstrインスタンス上format機能を使用するのと同じことを行うように見えます。しかし、私が気づいたことの1つは、このリテラル文字列の書式設定は、実際にはformatを呼び出すのに比べて非常に遅いということです。ここでtimeitは、それぞれの方法について述べているものです:

>>> x = 2 
>>> timeit.timeit(lambda: f"X is {x}") 
0.8658502227130764 
>>> timeit.timeit(lambda: "X is {}".format(x)) 
0.5500578542015617 

私はtimeitの引数として文字列を使用している場合は、私の結果はまだパターンを示している。

>>> timeit.timeit('x = 2; f"X is {x}"') 
0.5786435347381484 
>>> timeit.timeit('x = 2; "X is {}".format(x)') 
0.4145195760771685 

あなたが見ることができるように、formatを使用することはほとんど取ります半分の時間。文法が少ないので、リテラルメソッドが高速になると思います。リテラルメソッドが非常に遅くなるシーンの裏には何が起こっていますか?

+0

f-stringsは動的なので、すべてのループで文字列を生成する必要があります。書式文字列は、コードが実行される前に作成されたリテラルで、バイトコードに変換されます。 –

+0

@AlexHallこれは、 'x'が' format'メソッドに渡されたときローカル変数に代入されますが、 'globals'では' f "によって見つけ出さなければならないかもしれません..."構文。 – schwobaseggl

+1

@AlexHall:これはバグではありません。 'str.format()'は* runtime *のスロットを解析するのに対し、コンパイル時にはフォーマット文字列を解析する必要があるため、実装が異なるだけです。 –

答えて

23

注::この回答はPython 3.6 alphaリリース向けに書かれています。 A new opcode added to 3.6.0b1は、f-stringパフォーマンスを大幅に改善しました。


f"..."構文を効果{...}式の周りにリテラル文字列部分にstr.join()操作に変換され、それ自体がobject.__format__()方法(任意:..フォーマット仕様を通過する)を通過式の結果れます。分解するときは、これを見ることができます:

>>> import dis 
>>> dis.dis(compile('f"X is {x}"', '', 'exec')) 
    1   0 LOAD_CONST    0 ('') 
       3 LOAD_ATTR    0 (join) 
       6 LOAD_CONST    1 ('X is ') 
       9 LOAD_NAME    1 (x) 
      12 FORMAT_VALUE    0 
      15 BUILD_LIST    2 
      18 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      21 POP_TOP 
      22 LOAD_CONST    2 (None) 
      25 RETURN_VALUE 
>>> dis.dis(compile('"X is {}".format(x)', '', 'exec')) 
    1   0 LOAD_CONST    0 ('X is {}') 
       3 LOAD_ATTR    0 (format) 
       6 LOAD_NAME    1 (x) 
       9 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      12 POP_TOP 
      13 LOAD_CONST    1 (None) 
      16 RETURN_VALUE 

なお、結果でBUILD_LISTLOAD_ATTR .. (join)オペコード。新しいFORMAT_VALUEは、スタックの先頭にコンパイル時に解析された書式の値を加えて、object.__format__()呼び出しで結合します。これは、リストオブジェクトを作成し、str.join()メソッドを呼び出すためのPythonが必要であることを

''.join(["X is ", x.__format__('')]) 

注:

だからあなたの例、f"X is {x}"は、に変換されます。

str.format()呼び出しは、メソッドの呼び出しで、パース後も関与x.__format__('')への呼び出しがありますが、決定的に、ここでは関係なしリスト作成はありません。この違いは、str.format()メソッドを高速化します。

Python 3.6はアルファビルドとしてのみリリースされていることに注意してください。この実装はまだ簡単に変更できます。時間表の場合はPEP 494 – Python 3.6 Release Scheduleを参照してください。フォーマットされた文字列リテラルのパフォーマンスをさらに向上させる方法については、Python issue #27078(この質問に対する回答としてオープン)を参照してください。

+0

本当に素晴らしい説明、ありがとう!私は '__format__'という魔法の方法があることを知りませんでした。 –

+0

なぜ ''' .join([...])'に展開され、文字列連結ではないのですか? –

+3

@AlexHall:文字列連結にO(N^2)のパフォーマンス特性があるためです。 A + B + CはA + Bの文字列を最初に作成し、その結果をCとともに新しい文字列にコピーする必要があります。 –

16

3.6ベータ1より前は、フォーマット文字列f'x is {x}'''.join(['x is ', x.__format__('')])に相当するようにコンパイルされました。...

  1. は、それが文字列断片のシーケンスを建て
  2. ...と、このシーケンスは、リスト、タプルませんでした:結果のコードは、いくつかの理由で非効率的でした! (リストよりもタプルを作成する方が少し速い)。
  3. それは、それが__format__('')は常にため、self、または整数オブジェクトを返すことになるためにも裸Unicodeオブジェクト、上__format__を呼び出し、空の文字列
  4. join方法を見上げスタック
  5. に空文字列をプッシュ引数として__format__('')str(self)を返します。
  6. __format__メソッドはスロットされていません。後者のための文字列は、文字列がフォーマットされるたびに解釈されるため

しかし、より複雑で長い文字列リテラルフォーマットされた文字列はまだ、より速く対応する'...'.format(...)コールよりもされていると思います。


この非常に疑問が断片から文字列を連結する新しいPythonバイトコードのオペコードを求めissue 27078のための主要な動機でした。 Serhiy Storchakaはこの新しいオペコードを実装し、それをCPythonにマージして、Beta 1バージョン以降(そしてPython 3.6.0の最終版)からPython 3.6で利用できるようにしました。

結果として、リテラルの書式設定された文字列は、多くの場合、string.formatよりも速くになります。

>>> timeit.timeit("x = 2; 'X is {}'.format(x)") 
0.32464265200542286 
>>> timeit.timeit("x = 2; 'X is %s' % x") 
0.2260766440012958 
>>> timeit.timeit("x = 2; f'X is {x}'") 
0.14437875000294298 

f'X is {x}'

>>> dis.dis("f'X is {x}'") 
    1   0 LOAD_CONST    0 ('X is ') 
       2 LOAD_NAME    0 (x) 
       4 FORMAT_VALUE    0 
       6 BUILD_STRING    2 
       8 RETURN_VALUE 

新しいBUILD_STRINGにコンパイル:あなただけstrまたはintオブジェクトを補間している場合、これらは、また、Pythonの3.6で古いスタイルのフォーマットよりも速いことが多いくらいですFORMAT_VALUEの最適化に伴い、6つの非効率性の原因のうち最初の5つが完全に排除されています。 __format__のメソッドはまだスロット化されていないので、クラスの辞書検索が必要であるため、__str__を呼び出すよりも遅くなる必要がありますが、intまたはstrインスタンス(サブクラスではない)の一般的なケースでは、 !)を使用します。

0

これは、Python3.6リリースで解決されるように見えるだけの更新です。

>>> import dis 
>>> dis.dis(compile('f"X is {x}"', '', 'exec')) 
    1   0 LOAD_CONST    0 ('X is ') 
       2 LOAD_NAME    0 (x) 
       4 FORMAT_VALUE    0 
       6 BUILD_STRING    2 
       8 POP_TOP 
      10 LOAD_CONST    1 (None) 
      12 RETURN_VALUE 

>>> dis.dis(compile('"X is {}".format(x)', '', 'exec')) 
    1   0 LOAD_CONST    0 ('X is {}') 
       2 LOAD_ATTR    0 (format) 
       4 LOAD_NAME    1 (x) 
       6 CALL_FUNCTION   1 
       8 POP_TOP 
      10 LOAD_CONST    1 (None) 
      12 RETURN_VALUE 
+4

はい、Anttiの答えですでにカバーされているように、新しいオペコードが何をしているかを詳しく説明しています。 –

関連する問題