PythonのGarbage Collector

前回のsnmpによるシステム監視でメモリとスワップメモリを使い果たしてハングアップすることがわかりました。思い当たることは自ら作ったPythonスクリプトです。理屈上は定義した変数以外はないのでメモリの消費は一定のはずですが実際はどんどん増えて使い切ってしまう現象に陥りました。いままで何事もなく動いていたので何か原因があるはずです。しかしながら数千行のコードを調べるのは時間の無駄なので別の方法を調べてみました。検索するとPythonにガーベッジコレクションの機能があり、検索で実例をみて当てはめてみました。やはりPythonでループしていると起こり得るケースです。import gcでモジュールを読みこみループ上にgc.collect()を配置します。Pythonのドキュメントを見るとgc.enable()という機能があり、自動的にメモリを開放してくれるそうですが今回は適用しません。

追記:

gc.collect()を使うと50%程度とかなり遅くなることが分かりました。シュミレーションで使う場合はgc.enable()を使ったほうが良さそうです。いろいろアプリケーションを整理したらCactiが正常に動くようになりました。

 

追記2

まだPythonのバグがFIXできなかったのでデバッグをしてみました。検索すると何件かヒットしたので最初のひとつを挑戦してみました。

pip install objgraph

python3 -m pdb ./{program}.py

(Pdb) continue
CTRL+C
(Pdb) import objgraph
(Pdb) objgraph.show_most_common_types(limit=20)
function                   18919
tuple                      8785
dict                       7327
weakref                    4017
builtin_function_or_method 3585
...
(Pdb) continue
CTRL+C
(Pdb) import objgraph
(Pdb) objgraph.show_most_common_types(limit=20)
function                   18903
tuple                      8957
dict                       7326
weakref                    4018
builtin_function_or_method 3585
...

tupleが増えてることが分かります。tupleはあまり使っててないでたぶんモジュールかなとか推測できますがそれ以上進めません。

次にtracemallocを使った方法で挑戦してみました。

import tracemalloc
tracemalloc.start()
# ... run your application ...
...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
    print(stat)
[ Top 10 ]
/home/parvati/environments/test2_env/lib/python3.6/site-packages/mysql/connector/connection_cext.py:289: size=200 KiB, count=3633, average=56 B
/usr/lib/python3.6/tracemalloc.py:65: size=6336 B, count=88, average=72 B
/usr/lib/python3.6/tracemalloc.py:469: size=6224 B, count=91, average=68 B
...

/home/parvati/environments/test2_env/lib/python3.6/site-packages/mysql/connector/connection_cext.py:289: size=384 KiB, count=7019, average=56 B
/usr/lib/python3.6/tracemalloc.py:469: size=13.5 KiB, count=209, average=66 B
/usr/lib/python3.6/tracemalloc.py:462: size=10.1 KiB, count=208, average=50 B
...

connection_cext.pyのサイズが 200 KiBから384 KiBに増えていることがわかります。このプログラムを続けていると一定の割合で増加していきます。そこでonnection_cext.pyをエディタで開いてみました。

289行: row = self._cmysql.fetch_row()

とあります。class CMySQLConnection()内に定義された関数です。おそらくループでクラスで定義されたオブジェクトを消去せずオブジェクトを追加していったように推測できます。さらにGoogleで検索してみるとSELECT分はメモリに残るというようなコメントも見受けられます。となるとmysql-connector-pythonを編集するとなると手に負えないので、ほかpythonでMySQLが使えるモジュールを探してみてPyMySQLをインストールしてみました。

違いはmysql-connector-pythonはリストで返しますがPyMySQLはdict形式で返します。またmysql-connector-pythonに比べて半分くらいパーフォーマンスが落ちます。PyMySQLでプログラムを直してデバッグしてみます。

[ Top 10 ]
/usr/lib/python3.6/tracemalloc.py:469: size=16.1 KiB, count=251, average=66 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1211: size=14.1 KiB, count=150, average=96 B
/usr/lib/python3.6/tracemalloc.py:462: size=12.1 KiB, count=250, average=50 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1209: size=9672 B, count=403, average=24 B
/usr/lib/python3.6/tracemalloc.py:65: size=9000 B, count=125, average=72 B
./binance_histricalklinev2.py:144: size=6448 B, count=86, average=75 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/cursors.py:407: size=5032 B, count=70, average=72 B
...

[ Top 10 ]
/usr/lib/python3.6/tracemalloc.py:469: size=16.0 KiB, count=250, average=66 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1211: size=14.1 KiB, count=150, average=96 B
/usr/lib/python3.6/tracemalloc.py:462: size=12.1 KiB, count=249, average=50 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1209: size=9696 B, count=404, average=24 B
/usr/lib/python3.6/tracemalloc.py:65: size=9000 B, count=125, average=72 B
./binance_histricalklinev2.py:144: size=7128 B, count=95, average=75 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/cursors.py:407: size=5176 B, count=72, average=72 B

connections.py:1209cursors.py:407に若干増加が見られる程度となりました。これでしばらく様子見ようと思います。

 

参考: