質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

メモリリーク

メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

2回答

10884閲覧

Windows環境におけるPythonのメモリリークについて

elda

総合スコア34

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

メモリリーク

メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

3クリップ

投稿2017/07/07 18:00

編集2017/07/08 13:35

ソースコード

全文は長いので該当行のみ。
import漏れは補完いただけると幸いです。

Python

1import cv2 2import gc 3class Region: 4 def __init__(self, x, y, width, height): 5 self.X = x 6 self.Y = y 7 self.Width = width 8 self.Height = height 9 10 def size(self): 11 return (self.Width, self.Height) 12 def width(self): 13 return self.Width 14 def height(self): 15 return self.Height 16 17 def left(self): 18 return self.X 19 def right(self): 20 return self.X + self.Width 21 def top(self): 22 return self.Y 23 def bottom(self): 24 return self.Y + self.Height 25 26class SubImage: 27 _image_cache = ('', None) 28 def __init__(self, ID, image_prefix, region): 29 self.ID = ID 30 self.image_filename = image_prefix + '.jpg' 31 self.region = region 32 33 def get_image(self): 34 image = None 35 if self.image_filename == Target._image_cache[0]: 36 image = SubImage._image_cache[1] 37 else: 38 image = cv2.imread(self.image_filename)) 39 #del Target._image_cache 40 #gc.collect() 41 SubImage._image_cache = (self.image_filename, image) 42 return image[self.region.top():self.region.bottom(), self.region.left():self.region.right(), :] 43 44 @staticmethod 45 def deleteCache(): 46 #del Target._image_cache 47 #gc.collect() 48 SubImage._image_cache = ('', None) 49

疑問点

Pythonで大量の部分画像を生成しようとすると異常にメモリを消費してしまう。
コメントアウトしている以下の2文を追加するとこのメモリ消費が収まるが、どうして手動による解放が必要なのか理解できない。
ガーベジコレクションで自動解法されないのでしょうか?

メモリの消費についてより具体的に述べると、8000枚ほどの画像から5万枚の部分画像が必要な場合手動によるメモリ解放を行わない場合35.2GBのメモリを消費するのに対して、手動によるメモリ解放を行った場合16GBしか消費しない。

#コードについての補足
以下のようなデータベースから画像と部分画像を生成しようとしています。

画像のID画像ファイル名(拡張子なし)部分画像のx座標部分画像のy座標部分画像の幅部分画像の高さ
ID1ファイル名1x11y11w11h13
ID2ファイル名1x12y12w12h13
ID3ファイル名1x13y13w13h13
ID4ファイル名2x21y21w21h21
ID5ファイル名2x22y22w22h22

実際にこれを読み込んで

Python

1img = SubImage(ID1, ファイル名1, Region(x11, y11, w11, h11)) 2sub_image = img.get_image()

みたいにして使います。

#補足情報(言語/FW/ツール等のバージョンなど)
Windows環境

  • Windows 8.1/10
  • Python 3.6.2
  • OpenCV 3.2
  • numpy 1.11

よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

この問題は少し厄介ですね。説明が伝わるかわからないのですが、
OpenCVはPythonのライブラリではなく、C++で書かれたライブラリをラップしたライブラリです。ラッパーライブラリはうまくGCが働かなかったり、GCが効き過ぎたりとメモリで苦労することが多いです。

解決策としては、

  1. OpenCVを使わない
  2. 明示的にGC,あるいはdel

だと思います。

OpenCV以外のPythonで直接画像を扱うライブラリとしては、scikit-image などがあります。
sckit-imageの画像は numpy形式ですから、部分画像(質問のRegionの部分)はスライスで取り出してください。

投稿2017/07/07 20:39

MasashiKimura

総合スコア1150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

elda

2017/07/07 21:44 編集

ご回答ありがとうございます。 Python版のOpenCVでは画像のインタフェースとしてcv::Matではなくnumpy.ndarrayを用いています。 また、numpyは内部でC言語のライブラリを用いているため、貴方の仰るとおりであるならば、scikit-imageやnumpyを用いることは解決策にはなりませんね。
MasashiKimura

2017/07/08 00:54

あら、どうやらswigの問題と勘違いしました。
MasashiKimura

2017/07/08 00:59

では、単純にここに載っていないコードから参照があったり、循環参照していたりするのではないでしょうか?
elda

2017/07/09 15:43

ご回答ありがとうございます。 ここに載っていないコードからSubImage._image_cacheにアクセスすることはありません。 また、githubのissueを確認しましたが、該当するメソッドは使用していませんでした。
guest

0

そちらの環境を完全に再現することができないので、推測が入ります。
(提示されたコードがすべてではありませんよね?)

GCが行われない理由として考えられる事は以下の点です。

  • Target._image_cacheがどこかで参照されている
  • 循環参照を起こしている
  • C拡張部分のメモリリーク

gc.get_referrersで、他のオブジェクトから参照が取れます。
手動でGC解放を行っている部分でTarget._image_cacheが他から参照されていないか確認してください。
どうしても、他から参照する必要があるのであればweakrefモジュールで弱参照してください。

自分の記事で申し訳ありませんが、weakrefモジュールの使い方は以下の記事を参考にしてください。

http://qiita.com/pashango2/items/fb1e5e79589279c5a861

また、循環参照を起こしている場合ですが、PythonのGCでは循環参照オブジェクトでも解放可能です。

http://atsuoishimoto.hatenablog.com/entry/2013/12/06/000000

しかし、循環参照されたオブジェクトに__del__が設定されている場合は、リークする場合があります。
循環参照を起こしている場合、_image_cacheが__del__が定義されていないかを確認してください。

C拡張のメモリリークについては、ちょっと分かりかねます。
そこはOpenCVのバグの領域になりますので・・・

しかし、コードから推測するに、上記の2つなのではないかと思います。

投稿2017/07/08 03:52

編集2017/07/08 03:57
pashango2

総合スコア930

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

elda

2017/07/09 15:41

ご回答ありがとうございます。 メモリ解放の前後でget_referrersを用いて参照しているオブジェクトのリストを取得しようとしたので取得したオブジェクトは全てdict型のオブジェクトでした。(つまりgc.collectとdelを使ってもリファレンスは変わっていない) 具体的な中身としてはSubImageクラスのメソッドとクラス変数とその値がペアになったディクショナリでした。
pashango2

2017/07/10 01:51

つまり、他に強参照はなかったという事でしょうか? 気になるのは、解放に2行のコードがありますよね? #del Target._image_cache #gc.collect() gc.collect()のみだとメモリ使用量は変わるのでしょうか? 変わらない場合は、どこかで強参照されている可能性が高いです。
elda

2017/07/11 16:49

gc.collect()のみでメモリが開放されているようです。 これは、どこでも強参照されていないという認識で正しいのでしょうか?
pashango2

2017/07/12 01:32

gc.collect()のみでリーク?が改善された場合は、リークではなくGCの解放タイミングの違いということになります。
elda

2017/07/12 02:21

gcの開放タイミングの違いですか ということは今回のケースでは手動で開放するべきということでよろしいのでしょうか?
pashango2

2017/07/12 02:53

いえ、GC的には余裕があるから解放していないだけです。 宿題は夏休み最後にやるタイプです、GCが高負荷になるタイミングを手動で調整したいなら手動で解放するべきです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問