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

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

新規登録して質問してみよう
ただいま回答率
85.48%
メモリリーク

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

Python

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

Q&A

解決済

1回答

2592閲覧

Pythonでループしながらリストをextend続けた時、メモリ不足になってしまいます

fu_3823

総合スコア81

メモリリーク

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

Python

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

0グッド

0クリップ

投稿2022/07/13 11:51

編集2022/07/14 20:39

困っていること

Pythonでスクレイピングしたリストをextendして、大きなデータベースをつくっています。
大量にあるデータを、800回くらいのforループで回します。
この時、毎回extend後に、新しいリストが作られているようで、ループの途中でメモリリークを起こしてしまいます。
全て連結したあとのデータサイズは2GB程度で、ひとつひとつの小さなデータは大きくても数百KBです。きちんとメモリが解放されているようなら、問題なくプログラムは終了すると思うのですが、これはPythonの仕様なのでしょうか。

実現したいこと

メモリを解放するなどして、メモリの使用可能領域を適切に増やしたいです。

該当のソースコード

python

1def create_data(file_name): 2 with gzip.open(file_name, 'rt',) as f: 3 content = f.readlines() 4 content = [sub.split(",") for sub in content] 5 return content 6 7data = [] 8for f in files_list: 9 new_content = create_data(f) 10 data.extend(new_content) 11

要約したコードです。
create_data関数には他の処理も入りますが、そこは省略しました。確認のため、返りのファイルサイズを毎回取得すると、大きい場合は、例えば90MBなどのこともありますが、ほとんどが数百KBです。

また、一つ訂正です。
> 毎回extend後に、新しいリストが作られているようで、
と質問に書きましたが、オブジェクトにのidを確認すると、同じ値だったので、この認識は間違っていたようです。ですが、試しにextend()をコメントアウトして、ファイルのopenだけをループするとメモリは増えず、最後まで処理は回ります。問題の切り分けをして、extend()の箇所に問題があるのではないかと考えました。extend()すると、読み込んだファイル以上にメモリを消費してしまいます。

先ほど気がついたことですが、extend後のdataを__sizeof__()でチェックすると、ループの途中で増えていなことがあることに気づきました。170MBくらいから数ループ増えず、しばらくするとまた増えるということを繰り返します。ただ、len()でリストのサイズの確認すると毎回dataの長さは大きくなっています。sizeof()の結果は、逐次print()で出力しながらの確認です。
extemd後のdataの末尾は毎回変化していました。全体の内容がどうなっているかはこれからのチェックになりますが、ここまでの説明で問題の可能性を指摘していただける方はいないでしょうか。

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

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

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

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

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

meg_

2022/07/13 12:22

> 全て連結したあとのデータサイズは2GB程度で 上記はどうやって確認されましたか? またお使いのPCのメモリサイズはいくつでしょうか?
usekay

2022/07/13 13:00

実装中の処理はすべてのデータがメモリに載っていないといけないようなものでしょうか?
fu_3823

2022/07/13 13:19

>megさん 全データサイズを見積もるために、バイナリデータとして保存された個別のファイルサイズを全て取得して、足しました。概数を見積もるにはこれで良いと思っていましたが、openしたバイナリファイルをリストにして積算していくと、データのサイズは何倍にもなるのでしょうか。 メモリは16GBです。 このプログラムの実行前に、使用済みメモリは6GB程度です。ループを回している間に、メモリが圧迫され、300回ほど回ったあたりで使用済みメモリが14GBを超えます。 >usekayさん 今回は、全体を2GBと見積もったので、不具合なくメモリに乗るだろうと考えて、実装しました。
usekay

2022/07/13 13:57

やっているのはフォルダないなりファイルリスト上なりに記載された対象ファイルを一つ開く→ファイルの内用をすべて読み出し配列の1要素として追加→次のファイルを開く→ファイルの内用をすべて読み出し配列の要素として追加〜の繰り返しですか。 どういった方法でファイルを開いているか不明のためなんとも言えませんがopen関数の場合モードを指定しないとテキストファイルとして読み込むため消費メモリが増えたというのはありそうですね。 open関数の引数のmodeはバイナリ(B)入になっていますか?
fu_3823

2022/07/14 20:27

コードの一部を載せました。簡単なものなので、不要と思った部分ですが、バイナリを読み出す部分で勘違いがありました。読み込んでいたのはgzipファイルでした。読み出しはテキストで行なっています。
usekay

2022/07/14 21:54

gzipは自作のライブラリですか、それとも標準のライブラリをインポートしたものですか?
usekay

2022/07/14 22:02

そもそも何をしたいのでしょうか。gzipは圧縮ファイルなのでそのまま読み込んでも使えません。またバイナリファイルをテキストで読み込んだ場合データが化けてしまいます。 gzipを処理するライブラリを自作したいのでしょうか。
fu_3823

2022/07/15 13:36 編集

gzipはgzファイルを扱う標準ライブラリをインポートしたものです。 gzip.openしたファイルはテキストファイルでした。そのテキストがリストライクな形になっているので、データ成形の処理で整えてから、extendを繰り返します。そうすることで、大きなデータベースを作ろうとしています。例えば、株価データ1ヶ月分が格納されたファイルをextendして、1年分にまとめるというようなことをしています。 また、withブロックで読み込んだファイルですが、このコードではそのまま残ってしまうのでしょうか。
meg_

2022/07/15 23:31 編集

PythonのGCは遅いと感じる(解放が間に合わない)ことがあるので、手動でのコードを追加してみてはどうでしょうか?
usekay

2022/07/16 00:51

すみませんgzip.openのドキュメントを誤読していました。『gzip 圧縮ファイルをバイナリまたはテキストモードで開き、ファイルオブジェクト を返します。』ではなく『gzip 圧縮ファイル内のファイルをバイナリまたはテキストモードで開き、ファイルオブジェクト を返します。』とかにしてほしい…
usekay

2022/07/16 01:29

疑問点としては『全データサイズを見積もるために、バイナリデータとして保存された個別のファイルサイズを全て取得して、足しました。』というのがまず怪しいなと。gzファイルを解凍したファイルの合計容量ではなく圧縮ファイルの合計容量を計算した結果が2GBになっている気がします。ただこれだと展開後に20GBオーバーになる可能性があるかと思います。
usekay

2022/07/16 01:39

『例えば、株価データ1ヶ月分が格納されたファイルをextendして、1年分にまとめるというようなことをしています。』のまとめるとは単純に結合するということでしょうか。 言い換えると1日1件の株価データを365日分365件の株価データにするというようなことでしょうか。
usekay

2022/07/16 01:45

こんな感じで一度処理結果をファイルに書き出してみてはいかがでしょうか。 たぶん処理が進むにつれて大きくなっていくかと思います。 であればextendで配列に追加するのではなくファイルに書き出すべきでしょう。 そもそも10GBを超えるデータだと○○DBのようなツールを使うべきだとは思いますが。 import os def create_data(file_name): with gzip.open(file_name, 'rt',) as f: content = f.readlines() content = [sub.split(",") for sub in content] return content data = [] with open('dump.txt', mod='w') as f for f in files_list: new_content = create_data(f) # data.extend(new_content) print(new_content, file=f) print(os.gwd(), '\\', 'dump.txt', sep='')
jbpb0

2022/07/16 03:21

usekayさんも指摘してますが、 > 全データサイズを見積もるために、バイナリデータとして保存された個別のファイルサイズを全て取得して、足しました。 の「個別のファイルサイズ」がgzipで圧縮されてる状態でのファイルサイズなら、ダメだと思います pythonが読み込むデータは圧縮が解かれたものなので、読み込みたい全ファイルの圧縮を解いた時のサイズを調べて足さないといけないと思います あと、pythonが必要とするメモリー使用量とファイルサイズは同じではないと思うので、ファイルサイズが大きいものをいくつかpythonで読み込んだ時のメモリー使用量(の増加分)と、その時に読み込んだファイルの(圧縮されてない)サイズの合計から、メモリー使用量とファイルサイズの比を計算したらいいと思います その比に、読み込みたい全ファイルの圧縮を解いた時のサイズの合計をかけたものが、最大メモリー使用量の推定値になります
guest

回答1

0

ベストアンサー

まずGC(ガベージコレクタのことですよね)については雑に言ってしまえば変数に入っている物は対象外です。
おそらく単純に2GB程度のデータをメモリに格納できないだけだと思われます。
選択肢としては配列に入れずにジェネレーターとしてファイルのデータを読み出すようにするのが多分変更も少なく低コストですね。

投稿2022/07/13 12:29

usekay

総合スコア395

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

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

usekay

2022/07/14 21:55

まあバイナリファイルをテキストで読み込んでいるならさもありなんな展開ですね。
usekay

2022/07/14 21:59

メモリ消費が増えるのはフォイルを読み込んだままメモリに残しているからです。extendでリストが伸びるのはもとのリストに行単位でリストとしてリストとして読み込んだファイルを結合しているからです。 まあgzipを無理やりテキストとして読み込んだものを行単位のリストにする意味はおそらくありませんが。
usekay

2022/07/14 22:00

たまたま改行コードに見えるところで区切られていて配列が長くなっている、結果そのたびにオブジェクトが作られるのでメモリを過剰に消費するという展開ですね。
usekay

2022/07/14 22:03

パイソンは何であれオブジェクト指向的な意味でのオブジェクトとしてデータをメモリ上に格納するためデータ容量あたりのメモリ消費量でみた効率ははっきり言って悪い。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問