Pythonでの連結に_+
_または_+=
_を使用するのが非常に非効率的で悪い習慣である方法について、オンライン(Stack Overflowなど)で多くの情報を見つけることができます。
なぜ_+=
_がそれほど非効率的であるとは思えない。言及の外に ここ 「特定のケースで20%の改善のために最適化されている」(これらのケースが何であるかはまだ明確ではない)、私は追加情報を見つけることができません。
''.join()
を他のPython連結メソッドよりも優れたものにする、より技術的なレベルで何が起きているのでしょうか?
3つの文字列から文字列を作成する次のコードがあるとします。
_x = 'foo'
x += 'bar' # 'foobar'
x += 'baz' # 'foobarbaz'
_
この場合、Pythonは_'foobar'
_を割り当てて作成する前にまず_'foobarbaz'
_を割り当てて作成する必要があります。
したがって、呼び出される_+=
_ごとに、文字列の内容全体とそれに追加されるものはすべて、まったく新しいメモリバッファにコピーする必要があります。つまり、結合するN
文字列がある場合、およそN
の一時文字列を割り当てる必要があり、最初の部分文字列は〜N回コピーされます。最後のサブストリングは1回だけコピーされますが、平均して、各サブストリングは_~N/2
_回コピーされます。
_.join
_を使用すると、Pythonは中間文字列を作成する必要がないため、多くのトリックを再生できます。 CPython 必要なメモリ量を計算します最後に、適切なサイズのバッファを割り当て、最後に各ピースを新しいバッファにコピーします。つまり、各ピースは1回だけコピーされます。
場合によっては_+=
_のパフォーマンスを向上させる可能性のある他の実行可能なアプローチがあります。例えば。内部文字列表現が実際に rope
である場合、または一時文字列がプログラムにとって役に立たないことを何らかの形で理解し、それらを最適化するためにランタイムが実際に十分スマートである場合.
ただし、CPythonは確かにnotこれらの最適化を確実に行います(ただし、 少数の場合 )使用中の最も一般的な実装、多くのベストプラクティスは、CPythonに適したものに基づいています。標準化された一連の規範があることにより、他の実装でも最適化の取り組みに集中することが容易になります。
この動作は Luaの文字列バッファーの章 で最もよく説明されていると思います。
Pythonのコンテキストでその説明を書き換えるために、無邪気なコードスニペット(Luaのドキュメントにあるものの派生物)から始めましょう。
s = ""
for l in some_list:
s += l
各l
は20バイトであり、s
は既に50 KBのサイズに解析されていると仮定します。 Pythonがs + l
を連結すると、50,020バイトの新しい文字列が作成され、s
から50 KBがこの新しい文字列にコピーされます。つまり、新しい行ごとに、プログラムは50 KBのメモリを移動し、成長します。 100行(2 KBのみ)を読み取った後、スニペットはすでに5 MBを超えるメモリを移動しています。さらに悪いことに、割り当て後
s += l
古い文字列はゴミになりました。 2つのループサイクルの後、2つの古い文字列があり、合計で100 KBを超えるガベージが発生します。そのため、言語コンパイラはガベージコレクタを実行することを決定し、それらの100 KBを解放します。問題は、これが2サイクルごとに発生し、プログラムがリスト全体を読み取る前にガベージコレクターを2000回実行することです。このすべての作業を行っても、メモリ使用量はリストのサイズの大きな倍数になります。
そして、最後に:
この問題はLua固有のものではありません。真のガベージコレクションを備えた他の言語で、文字列が不変オブジェクトである場合、同様の動作を示します。Javaは最も有名な例です。 (Javaは、問題を改善するための構造
StringBuffer
を提供しています。)
Python文字列も 不変オブジェクト です。