3

私はしばらく前から、Python がメモリ内の文字列を複製する代わりに再利用することを好むことを知っていました。

>>> a = "test"
>>> id(a)
36910184L
>>> b = "test"
>>> id(b)
36910184L

しかし、私は最近、から返される文字列がraw_input()その典型的な最適化パターンに従っていないことを発見しました。

>>> a = "test"
>>> id(a)
36910184L
>>> c = raw_input()
test
>>> id(c)
45582816L

なぜそうなのか気になりますか?技術的な理由はありますか?

4

3 に答える 3

3

私には、python は文字列リテラルをインターンしているように見えますが、他のプロセスで作成された文字列はインターンされません。

>>> s = 'ab'
>>> id(s)
952080
>>> g = 'a' if True else 'c'
>>> g += 'b'
>>> g
'ab'
>>> id(g)
951336

もちろん、raw_inputは文字列リテラルを使用せずに新しい文字列を作成しているため、同じid. C-python が文字列をインターンする理由は (少なくとも) 2 つあります。メモリ (同じもののコピーを大量に保存しなくても、大量に保存できます) と、ハッシュ衝突の解決です。2 つの文字列が同じ値にハッシュされる場合 (たとえば、辞書検索で)、Python は両方の文字列が同等であることを確認する必要があります。それらがインターンされていない場合は文字列比較を実行できますが、インターンされている場合は、ポインター比較のみを実行する必要があり、これは少し効率的です。

于 2013-02-16T01:35:43.973 に答える
2

コンパイラはintern、実際のソース コード (つまり、文字列リテラル) に存在する場所を除いて、文字列を作成できません。それに加えて、raw_input新しい行も削除します。

于 2013-02-16T01:35:52.960 に答える
2

[更新] 質問に答えるには、Python が文字列を再利用する理由、方法、時期を知る必要があります。

方法から始めましょう: Python は「interned」文字列を使用します -ウィキペディアから:

コンピューター サイエンスでは、文字列のインターニングは、不変でなければならない個別の文字列値のコピーを 1 つだけ格納する方法です。文字列をインターンすると、一部の文字列処理タスクの時間またはスペース効率が向上しますが、文字列が作成またはインターンされるときに必要な時間が長くなります。個別の値は文字列インターン プールに格納されます。

なんで?ここでのメモリの節約は主な目的ではなく、良い副作用にすぎないようです。

文字列インターニングは、文字列キーを持つハッシュ テーブルに大きく依存するアプリケーション (コンパイラや動的プログラミング言語ランタイムなど) でパフォーマンスのボトルネックになることがある文字列比較を高速化します。インターンなしでは、2 つの異なる文字列が等しいことを確認するには、両方の文字列のすべての文字を調べる必要があります。これはいくつかの理由で遅くなります。文字列の長さは本質的に O(n) です。通常、メモリの複数の領域からの読み取りが必要であり、時間がかかります。読み取りによってプロセッサのキャッシュがいっぱいになるため、他のニーズに使用できるキャッシュが少なくなります。インターンされた文字列を使用すると、元のインターン操作の後に単純なオブジェクト アイデンティティ テストで十分です。これは通常、ポインターの等価性テストとして実装されます。通常は、メモリ参照がまったくない単一のマシン命令です。

同じ文字列値のインスタンスが多数ある場合、文字列のインターンもメモリ使用量を削減します。たとえば、ネットワークまたはストレージから読み取られます。このような文字列には、マジック ナンバーやネットワーク プロトコル情報が含まれる場合があります。たとえば、XML パーサーは、メモリを節約するためにタグと属性の名前をインターンする場合があります。

"when": cpython は、次の状況で文字列を "interns" します:

  • intern()必須ではない組み込み関数 ( sys.internPython 3 に移動)を使用する場合。
  • 小さな文字列 (0 または 1 バイト) - Laurent Luce によるこの非常に有益な記事では、実装について説明しています
  • Python プログラムで使用される名前は自動的にインターンされます
  • モジュール、クラス、またはインスタンスの属性を保持するために使用される辞書には、キーがインターンされています

他の状況では、すべての実装で、文字列が自動的にインターンされるタイミングが大きく異なるようです。

アレックス・マルティネリこの回答で行ったよりもうまく言えませんでした(この男が245kの評判を持っているのも不思議ではありません):

Python 言語の各実装では、不変オブジェクト (文字列など) を割り当てる際に独自のトレードオフを自由に行うことができます。新しいオブジェクトを作成するか、既存の等しいオブジェクトを見つけてそれへの参照をもう 1 つ使用するかは、言語の実装から問題ありません。視点。もちろん、実際には、実際の実装では合理的な妥協が必要です。適切な既存のオブジェクトを見つけるときに、適切な既存のオブジェクトをもう 1 つ参照するのは安価で簡単です。存在しない可能性があります) 検索に時間がかかる可能性があるようです。

したがって、たとえば、単一の関数内で同じ文字列リテラルが複数回出現する場合は、(私が知っているすべての実装で) 「同じオブジェクトへの新しい参照」戦略を使用します。重複を避ける。しかし、別々の関数でこれを行うと、非常に時間がかかる可能性があるため、実際の実装では、まったく行わないか、合理的なトレードオフが期待できるヒューリスティックに識別されたケースのサブセットでのみ行うかのいずれかです。コンパイル時間 (同一の既存の定数を検索することで遅くなる) とメモリ消費量 (定数の新しいコピーが作成され続けると増加する)。

ファイルからデータを読み取るときに、可能性のある重複を特定する(複数の参照を介して単一のオブジェクトを再利用する)のに苦労するPython(または、Javaなどの定数文字列を持つ他の言語)の実装を知りません- -それは有望なトレードオフではないようです(そして、ここではコンパイル時間ではなくランタイムを支払うことになるため、トレードオフはさらに魅力的ではありません)。もちろん、(アプリケーション レベルの考慮事項のおかげで) そのような不変オブジェクトが大きく、多くの重複が発生しやすいことがわかっている場合は、独自の "定数プール" 戦略を非常に簡単に実装できます (文字列に対してはインターンが役立ちますが、たとえば、不変のアイテムを含むタプル、巨大な長整数などを独自に作成するのは難しくありません)。


【一次回答】

これは回答というよりはコメントですが、コメント システムはコードの投稿にはあまり適していません。

def main():
    while True:
        s = raw_input('feed me:')
        print '"{}".id = {}'.format(s, id(s))

if __name__ == '__main__':
    main()

これを実行すると、次のようになります。

"test".id = 41898688
"test".id = 41900848
"test".id = 41898928
"test".id = 41898688
"test".id = 41900848
"test".id = 41898928
"test".id = 41898688

私の経験から、少なくとも 2.7 では、raw_input().

実装でハッシュ テーブルを使用している場合は、複数あると思います。さっそく源泉に潜ります。

【初回更新】

私の実験に欠陥があったようです:

def main():
    storage = []
    while True:
        s = raw_input('feed me:')
        print '"{}".id = {}'.format(s, id(s))
        storage.append(s)

if __name__ == '__main__':
    main()

結果:

"test".id = 43274944
"test".id = 43277104
"test".id = 43487408
"test".id = 43487504
"test".id = 43487552
"test".id = 43487600
"test".id = 43487648
"test".id = 43487744
"test".id = 43487864
"test".id = 43487936
"test".id = 43487984
"test".id = 43488032

別の質問への回答で、ユーザーtzotはオブジェクトの有効期間について次のように警告しています。

補足: Python でオブジェクトの有効期間を知ることは非常に重要です。次のセッションに注意してください。

Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10) 
[GCC 4.3.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a="a"
>>> b="b"
>>> print id(a+b), id(b+a)
134898720 134898720
>>> print (a+b) is (b+a)
False

2 つの別々の式の ID を出力して、「2 つの式は等しい/同等/同じでなければならないので、それらは等しい」と注意するというあなたの考えは誤りです。出力の 1 行は、必ずしもすべてのコンテンツが同じ瞬間に作成および/または共存したことを意味するわけではありません。

2 つのオブジェクトが同じオブジェクトかどうかを知りたい場合は、Python に直接 (is演算子を使用して) 問い合わせてください。

于 2013-02-16T01:41:56.753 に答える