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には、メモリの取得・開放を行うAPIに PyMem*
系、PyObect*
系、PyMem_Raw*
系の3種類がある。PyMem*
系のAPIは古くからあるAPIで、C言語ランタイムライブラリの malloc()
や free()
などをそのまま使用している。
一方、PyObject
系のメモリ操作APIは、一般に pymalloc
と呼ばれるPython専用のメモリアロケータを使用しており、Pythonでのメモリ利用形態に合わせて、小さくて数の多いメモリ割り当てを効率良く行えるようにしている。
pymalloc
が導入された頃は、すべてのメモリ割り当てをpymalloc
にしてしまうとサードパーティ製の拡張モジュールなどで動作しなくなるケースが多く、完全に pymalloc
に切り替えることはできなかった。そこで、PyMem*
系のAPIは変更せずに malloc()
を使用する実装のまま残され、新しいコードでは PyObject
系APIを使用するようになった。
しかし、pymalloc
導入からもう20年近く経過し、この間にpymalloc
が進化したこともあって,そろそろPyMem*
系APIでも pymalloc
を使用してもよいのではないか、ということになった。そこで3.6から PyObject
系APIと同じくpymalloc
を使用するように変更された。この変更により、実質的に、PyMem
系APIは必要なくなっている。
既存の拡張モジュールなどでは、正しくPyMem*
系APIを使用していれば、この修正による不具合は発生しないはずだ。現在、C言語などによる拡張モジュールを開発している場合、正しくメモリ管理APIを利用できているか自信がなければ、デバッグビルドのPythonでテストしてみると、GILを確保しない状態でのAPI呼び出しなどの不正を検出できるので試してみると良いかもしれない。