[解決済み] インタプリタが保持する整数キャッシュはどうなっているのか?
質問
Pythonのソースコードに潜ったところ、Pythonの配列は
PyInt_Object
の配列を保持していることがわかりました。
int(-5)
から
int(256)
(@src/Objects/intobject.c) のようになります。
ちょっとした実験で証明されます。
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
しかし、これらのコードをpyファイルの中で一緒に実行すると(あるいはセミコロンで結合すると)、結果は違ってきます。
>>> a = 257; b = 257; a is b
True
なぜ同じオブジェクトのままなのか気になったので、構文ツリーとコンパイラを深掘りしてみると、以下のような呼び出し階層になりました。
PyRun_FileExFlags()
mod = PyParser_ASTFromFile()
node *n = PyParser_ParseFileFlagsEx() //source to cst
parsetoke()
ps = PyParser_New()
for (;;)
PyTokenizer_Get()
PyParser_AddToken(ps, ...)
mod = PyAST_FromNode(n, ...) //cst to ast
run_mod(mod, ...)
co = PyAST_Compile(mod, ...) //ast to CFG
PyFuture_FromAST()
PySymtable_Build()
co = compiler_mod()
PyEval_EvalCode(co, ...)
PyEval_EvalCodeEx()
次に、デバッグ用のコードを
PyInt_FromLong
の前と後に
PyAST_FromNode
で、test.pyを実行しました。
a = 257
b = 257
print "id(a) = %d, id(b) = %d" % (id(a), id(b))
のように出力されます。
DEBUG: before PyAST_FromNode
name = a
ival = 257, id = 176046536
name = b
ival = 257, id = 176046752
name = a
name = b
DEBUG: after PyAST_FromNode
run_mod
PyAST_Compile ok
id(a) = 176046536, id(b) = 176046536
Eval ok
つまり
cst
から
ast
を変換すると、2つの異なる
PyInt_Object
が作成されます (実際には、この処理は
ast_for_atom()
関数で実行されます) が、それらは後でマージされます。
のソースを理解するのは難しいですね。
PyAST_Compile
と
PyEval_EvalCode
ということで、助けを求めに来ました。もし誰かがヒントをくれるなら、感謝します。
どのように解決するのですか?
Pythonは範囲内の整数をキャッシュしています。
[-5, 256]
の範囲にある整数をキャッシュするので、その範囲にある整数は通常
であるが、常に
を同一とする。
257で表示されているのは、同じコードオブジェクトでコンパイルされたときにPythonコンパイラが同一のリテラルを最適化するものです。
Pythonシェルで入力する場合、各行は完全に異なるステートメントであり、別々にパースされ、コンパイルされます、したがって。
>>> a = 257
>>> b = 257
>>> a is b
False
しかし、同じコードをファイルにすると
$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True
これはコンパイラがリテラルをまとめて解析する機会があるとき、例えば対話型インタプリタで関数を定義するときに起こります。
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
コンパイルされたコードに含まれる定数が1つであることに注意してください。
257
.
結論として、Pythonのバイトコードコンパイラは(静的型付け言語のような)大規模な最適化を行うことはできませんが、あなたが思っている以上のことを行っています。そのひとつが、リテラルの使い方を分析し、重複を避けることです。
これはキャッシュを持たないfloatでも動作するため、キャッシュとは関係ないことに注意してください。
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
タプルのようなより複雑なリテラルでは、"doesn't work"です。
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
しかし、タプル内のリテラルは共有されます。
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
(定数折りたたみとのぞき穴オプティマイザはバグフィックスバージョン間でも挙動が変わることがあるので、どの例も
True
または
False
は基本的に任意であり、将来的に変更される可能性があります)。
その2つを見る理由について
PyInt_Object
が作成されることについてですが、私は
と推測します。
は、リテラルな比較を避けるために行われるのだと思います。
257
は複数のリテラルで表現することができます。
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
パーサーは2つの選択肢を持っています。
- 整数を作成する前にリテラルを共通のベースに変換し、リテラルが等価であるかどうかを確認します。
- 整数オブジェクトを作成し、それらが等価であるかどうかを確認します。そうでなければ、すでに割り当てるべき整数があります。
おそらくPythonパーサーは2番目のアプローチを使用します。これは変換コードの書き換えを避けることができ、また拡張も簡単です(たとえば、floatでも動作します)。
を読むと
Python/ast.c
ファイルを読むと、すべての数字をパースする関数は
parsenumber
で、これは
PyOS_strtoul
を呼び出して整数値を取得し(整数値用)、最終的に
PyLong_FromString
:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
ここでわかるように、パーサは ではなく をチェックしないので、int オブジェクトが 2 つ作成されていることがわかります。 つまり、パーサーは最初に定数を作成し、その後でバイトコードを最適化して同じ定数に同じオブジェクトを使用するのです。
このチェックを行うコードは
Python/compile.c
または
Python/peephole.c
これらはASTをバイトコードに変換するファイルであるためです。
特に
compiler_add_o
関数がそれを行うようです。にこんなコメントがあります。
compiler_lambda
:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
ということで、どうやら
compiler_add_o
は関数/ラムダなどの定数を挿入するために使われます。
その
compiler_add_o
関数は、定数を
dict
オブジェクトに格納します。このことから、同じ定数は同じスロットに入ることになり、最終的なバイトコードには1つの定数が含まれることになります。
関連
-
[解決済み] Pythonで型をチェックする標準的な方法は何ですか?
-
[解決済み] リストとタプルの違いは何ですか?
-
[解決済み] PythonモジュールとPythonパッケージの違いは何ですか?
-
[解決済み] Cache-Control: max-age=0とno-cacheの違いは何ですか?
-
[解決済み] Pythonの短いリストにプリペンドするための慣用的な構文は何ですか?
-
[解決済み] Pythonインタプリタのフルパスを検索しますか?
-
[解決済み] インタプリタのコンソールをクリアする方法を教えてください。
-
[解決済み】CPythonのグローバルインタープリターロック(GIL)とは何ですか?
-
[解決済み] Pythonのキャッシュライブラリはありますか?
-
[解決済み] 辞書のキーと値を交換するにはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Pythonのargparseを使った隠し引数の作成
-
[解決済み] スペースがないテキストを単語のリストに分割する方法
-
[解決済み] 範囲指定された浮動小数点数のランダムな配列を生成します。
-
[解決済み] あるオブジェクトが数であるかどうかを確認する、最もパイソン的な方法は何でしょうか?
-
[解決済み] matplotlib でプロットの軸、目盛、ラベルの色を変更する方法
-
[解決済み] Pythonで、ウェブサイトが404か200かを確認するためにurllibをどのように使用しますか?
-
[解決済み] Pythonでファイルの読み込みと上書きをする
-
[解決済み] PythonのRequestsモジュールを使ってWebサイトに "ログイン "するには?
-
[解決済み] Pythonの辞書にあるスレッドセーフについて
-
[解決済み] 認証プラグイン 'caching_sha2_password' はサポートされていません。