Python 3.6の概要 (最終回 - オプティマイズ)

Python 3.6では、長年に渡って使われてきた、Python の基本的な実装に関わる部分で、重要な変更が行われている。

辞書オブジェクトのレイアウト変更

Raymond Hettingerのアイデア を元に、稲田さん が実装したもので、古くから使われてきたPythonの辞書オブジェクトが修正され、よりメモリ効率の良い形式でデータを格納するように変更された。

この修正により、辞書オブジェクトのメモリ使用量が20〜25%程度改善されている。

バイトコードからワードコードへ

Pythonインタープリタは、ソースコードバイトコード と呼ばれる中間言語に変換し、実行する仕組みになっている。従来、バイトコード はその名の通り8bit長のデータの集まりだったが、Python3.6では、バイトコードのサイズは8bitから16bitに拡張された。

これまでのバイトコードでは 命令コード+可変長の引数 という形式の中間言語を解釈しながら実行していたが、新しい形式では、ほぼ固定長の中間言語を読み込んで実行できるようになり、Pythonインタープリタがシンプルに、効率よく動作するようになった。(https://bugs.python.org/issue26647)

PyMem_* APIが pymalloc を使用

Python C APIには、メモリの取得・開放を行うAPIPyMem*系、PyObect*系、PyMem_Raw*系の3種類がある。PyMem*系のAPIは古くからあるAPIで、C言語ランタイムライブラリの malloc()free() などをそのまま使用している。

一方、PyObject系のメモリ操作APIは、一般に pymalloc と呼ばれるPython専用のメモリアロケータを使用しており、Pythonでのメモリ利用形態に合わせて、小さくて数の多いメモリ割り当てを効率良く行えるようにしている。

pymalloc が導入された頃は、すべてのメモリ割り当てをpymallocにしてしまうとサードパーティ製の拡張モジュールなどで動作しなくなるケースが多く、完全に pymalloc に切り替えることはできなかった。そこで、PyMem*系のAPIは変更せずに malloc()を使用する実装のまま残され、新しいコードでは PyObjectAPIを使用するようになった。

しかし、pymalloc 導入からもう20年近く経過し、この間にpymallocが進化したこともあって,そろそろPyMem*APIでも pymallocを使用してもよいのではないか、ということになった。そこで3.6から PyObjectAPIと同じくpymallocを使用するように変更された。この変更により、実質的に、PyMemAPIは必要なくなっている。

既存の拡張モジュールなどでは、正しくPyMem*APIを使用していれば、この修正による不具合は発生しないはずだ。現在、C言語などによる拡張モジュールを開発している場合、正しくメモリ管理APIを利用できているか自信がなければ、デバッグビルドのPythonでテストしてみると、GILを確保しない状態でのAPI呼び出しなどの不正を検出できるので試してみると良いかもしれない。

https://bugs.python.org/issue26249