追記2
こちらの記事に汎用的な対処法が記載されています。
matplotlib.pyplot のメモリリークの対処法 - Qiita
注意(追記1)
本記事で紹介する実験では、plt.clf() → plt.close()とすればメモリが解放されるという結果になりました。しかし、pltの使い方によっては、plt.clf() → plt.close()としてもメモリが解放されない場合がある事が後に判明しました。そのため、当記事はpltにおけるメモリ解放の挙動を理解するに当たっての参考として利用いただければと思います。
なお、事例としては、plt.tight_layout()やplt.savefig()をするとメモリが解放されない様です。この場合、もしJupyter上で実行しているのではれば、別のセルでgc.collect()をする事で、kernelを再起動せずに大半のメモリを解放できる事を筆者は確認しています。(一方、同じセルでplt.clf() → plt.close()をした直後にgc.collect()をした場合は、メモリは解放されていませんでした。)
要約
# NG
# メモリが解放されない
plt.close()
# OK
# メモリが解放される
plt.clf()
plt.close()
# NG
# 順番を逆にするとメモリが解放されない
# plt.close() で figure を閉じると、描画内容を clear 出来なくなるからだろう。
plt.close()
plt.clf()
経緯
一度に数千枚のグラフを描画していた所、out of memory エラーが出てしまいました。 毎回plt.close()をしていたので何故だろうと思い以下の実験方法で調べました。その結果、plt.close()だけではメモリが解放されない事が分かりました。そして、plt.clf() → plt.close()とすればメモリが解放される事が分かりました。
実験方法
メモリサイズが大きいグラフを10回連続でプロットします。
各プロット終了時のメモリ使用量を記録しておき、プロット回数とメモリ使用量の関係を調べます。
この手順を以下の4パターンで行います。
| 後処理の方法 | |
|---|---|
| パターン1 | plt.clf() |
| パターン2 | plt.clf() → plt.close() |
| パターン3 | plt.close() |
| パターン4 | plt.close() → plt.clf() |
ただし、パターンごとに毎回 kernel を restart します。 メモリ使用量のベースラインを揃えるためです。
これを行うコードは以下の通りです。
import matplotlib.pyplot as plt
import numpy as np
import psutil
mem_ary = []
# 10回プロットする
for i in range(10):
# メモリサイズが大きいグラフを描画
x = np.arange(1e7)
y = np.arange(1e7)
plt.plot(x, y)
# ===================================================
# 以下のパターン1からパターン4のいずれか1つを実行する
# 残りはコメントアウトする
# ===================================================
# パターン1
plt.clf()
# パターン2
plt.clf()
plt.close()
# パターン3
plt.close()
# パターン4
plt.close()
plt.clf
# ===================================================
# メモリ使用量を記録
mem = psutil.virtual_memory().used / 1e9
mem = round(mem, 1)
mem_ary.append(mem)
結果と結論
結果をグラフにまとめると次の様になりました。
plt.clf() → plt.close()だけ、メモリ使用量が増加していません。従って、plt.clf() → plt.close()とするべき事が分かります。
また、順番を逆にしてplt.close() → plt.clf()とするとメモリが解放されなくなっています。注意した方がいいでしょう。原因は、おそらくplt.close()で figure を閉じると、描画内容を clear 出来なくなるからだと思います。(参考:plt.close()の公式DOC、plt.clf()の公式DOC)
バージョン
matplotlib:3.2.1