idとctypesを用いてメモリの中を確認
Pythonで a = 10
と変数に値を代入したとき、次のように動作する。
- メモリ上の番地 0x555f73c4bb20 にオブジェクトとしての数値 10 が格納される
- 変数 a がメモリ上の番地 0x555f73c4bb20 を参照する
a = 10
# id関数でメモリの番地が分かる # https://docs.python.org/2/library/functions.html#id
print(id(a)) # 93868452526880 10進数のメモリ番地
print(hex(id(a))) # 0x555f73c4bb20 16進数のメモリ番地
なお、-5〜256の整数はPythonを起動したときにメモリ上に乗せられるのでステップ1が省略される。この範囲である理由はおそらく使用頻度が高いから。以下のidの逆関数を用いることで-5〜256の整数のidが等間隔(32ずつ)になっており、あらかじめメモリに乗っていることが確認できる。
import ctypes
# idの逆関数でメモリの番地からデータを取得できる
bi = lambda x:ctypes.cast(x, ctypes.py_object).value
# id番号からデータを取ってくる
bi(id(10) + 32) # 11
32ずつの間隔について気になったため、メモリ上でどのようにオブジェクトを配置しているのかを調べてみる。
まず、メモリとは次のような構成になっており、1つの番地に1byteを格納できる。
たいていは、オブジェクトに関する情報とともに、複数の番地にまたがって値を格納することになる。
オブジェクトがメモリ空間上でいくつの番地(何バイト)を専有しているかを次のように調べた。
import sys
# sys.getsizeof()でbyte数を確認できる
# https://docs.python.org/3/library/sys.html#sys.getsizeof
print(sys.getsizeof(10)) # 28byte
print(sys.getsizeof(2**30 - 1)) # 28byte
print(sys.getsizeof(2**30)) # 32byte
print(sys.getsizeof(2**60 - 1)) # 32byte
print(sys.getsizeof(2**60)) # 36byte
print(sys.getsizeof("")) # 49byte 文字列型の初期化は49バイト
print(sys.getsizeof("a")) # 50byte
print(sys.getsizeof("ai")) # 51byte
print(sys.getsizeof("1")) # 50byte
print(sys.getsizeof("あ")) # 76byte 日本語だと初期バイトが26増える
print(sys.getsizeof("あい")) # 78byte 一文字につき2バイト増える
print(sys.getsizeof("漢")) # 76byte
print(sys.getsizeof("漢字")) # 78byte 一文字につき2バイト増える
「整数の10なんか1byteで収まるやんけ、なんで28byteも使ってるんだ、28bitの間違いでは?」と思ったが、オブジェクトととしての数値は、単なる数ビットの数値という以外にも、色んな機能を持っているから28byteもかかっているのだろう。
自分のPCはメモリが8GBで8,000,000,000個くらいの番地があるため、1つの値に50byteかかるとすると、160,000,000個までの値をメモリに乗せることができる。
試しに以下のコードを実行すると、がっつりフリーズした。
over_list = list(range(10**9))
CPUとレジスタ、メモリの動作
プログラムを実行すると、ソースコードはコンピュータが理解しやすい「機械語」へと翻訳される。(Pythonの場合は一行ずつ翻訳するインタープリタ方式で、C言語は全体をまとめて翻訳するコンパイル方式)
また、CPUの内部にはレジスタと呼ばれる、メモリと比べると小さいが、CPUの演算装置に直結した記憶回路がある。(メモリの番地の数は数十億個で、レジスタは数十個)
メモリはRAMというコンデンサベースの回路、レジスタはフリップフロップというトランジスタベースの回路であり、それぞれ利点・欠点があるため、コンピュータはそれを組み合わせて適材適所で利用している。
例えば単純な加算の命令はメモリ・レジスタ・演算装置を用いて次のように4つの命令としてコンピュータで実行される。各命令はメモリに保存されており、メモリの番地順に取り出される。
- メモリの番地AAAにあるデータをレジスタ1に取ってくる
- メモリの番地BBBにあるデータをレジスタ2に取ってくる
- レジスタ1とレジスタ2の値をCPUの演算装置で足してレジスタ3に保存する
- レジスタ3のデータをメモリ上の番地CCCに保存する
これを踏まえると、ソースコードは以下のように機械語に置き換わる。
a = 1
b = 2
c = a + b
# メモリから a = 1 という命令をCPUが読み取る
# メモリ番地AAAに1を格納
# メモリから b = 2 という命令をCPUが読み取る
# メモリ番地BBBに2を格納
# メモリから c = a + b という命令をCPUが読み取る
# メモリの番地AAAにあるデータをレジスタ1に取ってくる
# メモリの番地BBBにあるデータをレジスタ2に取ってくる
# レジスタ1とレジスタ2の値をCPUの演算装置で足してレジスタ3に保存する
# レジスタ3のデータをメモリ上の番地CCCに保存する