🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Google Colaboratory

Google Colaboratoryとは、無償のJupyterノートブック環境。教育や研究機関の機械学習の普及のためのGoogleの研究プロジェクトです。PythonやNumpyといった機械学習で要する大方の環境がすでに構築されており、コードの記述・実行、解析の保存・共有などが可能です。

メモリリーク

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

Python

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

Q&A

解決済

2回答

15318閲覧

メモリが一気に数GB食われる原因と対策

mongaa

総合スコア2

Google Colaboratory

Google Colaboratoryとは、無償のJupyterノートブック環境。教育や研究機関の機械学習の普及のためのGoogleの研究プロジェクトです。PythonやNumpyといった機械学習で要する大方の環境がすでに構築されており、コードの記述・実行、解析の保存・共有などが可能です。

メモリリーク

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

Python

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

1グッド

4クリップ

投稿2021/01/11 11:27

編集2021/01/11 11:40

Google Colab.の無料GPUでプログラムを実行したところ、メモリ(RAM:12.72GB)が一気に消費されてしまいます。
原因と対策についてご教授いただきたいです。

・コード

Python

1#サマータイム期間外ならデータの時刻を1時間早くする(=サマータイムの時間にあわせる) 2import time 3new = [] 4new_append = new.append 5elapsed_time = 0 6t1 = time.time() 7 8def Summer(): 9 for i,x in enumerate(data["summer"]): 10 if x == "EST": # ESTなら1時間データを早くする 11 new_append(data.iloc[i,0:1]+timedelta(hours=-1)) 12 new[-1]["time"]=str(new[-1]["time"]) #上で型がdatetime64になるので戻す 13 else: # EDTならそのまま 14 new_append(data.iloc[i,0:1]) 15 if i%20000==0: #進捗確認用に20000データ毎に出力 16 print(i) 17 18Summer() 19new = pd.DataFrame(new) #【※】ここでメモリ消費が急増 20data.iloc[:,0:1] = new["time"] 21 22t2 = time.time() # 処理後の時刻 23elapsed_time = t2-t1 # 経過時間 24print(f"経過時間:{elapsed_time}") # 経過時間を表示

inputは以下のようなデータです。
・data(長さは実際は400,000程度)

timesummer
02019-10-10 06:02:00EDT
12020-11-23 06:02:00EST

コードの【※】の箇所で120s程度の時間がかかり、メモリ消費が著しく増大します。
dataもnewのバイト数を調べてみましたが、数MB程度でデータ長が原因ではないのかな?と考えております。
(ちなみに、inputの長さが1,000,000を超えたものにすると、クラッシュします・・・)

・原因の確認をしたくて試したこと
1. 実行後に別のセルにて、先ほど宣言した変数をdelで消去しましたが、【※】で増大した分のメモリは解放されませんでした。
→やはり、変数のデータの長さは関係なさそう?
2. 下記のように一旦別の変数をかませてみてもメモリの増大量はほぼ変わりませんでした。
下記のコードの場合、2行目のnew1 = pd.DataFrame(new1)でメモリが増大します。

Python

1new1 = new.copy() 2new1 = pd.DataFrame(new1) 3new = data.iloc[:,0:1]
A_kirisaki👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

解決策はDaregadaさんが書かれているので、原因について解説します。

こちらには再現する環境がありませんので推測になりますが、以下のような問題が原因と考えます。

原因1 現在のCpythonのガーベジコレクションの実装が貧弱(mallocまかせ)である。
原因2 ソースコードの誤りで不必要な細かいオブジェクトを大量に生成した。
原因3 dataがかなりのメモリを消費している。
原因4 大量のデータシーケンスのリストを与えられて新規のデータフレームを作成するときに一時的に作られるオブジェクトが大量のメモリを消費する。

このため、回収はしたものの再利用できないメモリが大量に発生している。

原因2の該当箇所

python

1 if x == "EST": # ESTなら1時間データを早くする 2 new_append(data.iloc[i,0:1]+timedelta(hours=-1)) 3 new[-1]["time"]=str(new[-1]["time"]) #上で型がdatetime64になるので戻す 4 else: # EDTならそのまま 5 new_append(data.iloc[i,0:1])

new_appendするのは時間だけで良いにもかかわらず、時間を含むシーケンスとなっている。
メモリ不足には影響しないが、シーケンスの値はESTの時には文字列。EDTの時にはdatetime64である。

データフレームオブジェクトはindex、columns、valuesを持ち、さらに、indexとcolumnsもそれぞれvaluesを持っている。
valuesはnumpyのndarrayであり、その型がオブジェクト型であれば、さらにその中にあるオブジェクトもメモリを消費する。
以下はメモリ消費の一例です。実際には8バイト境界で確保されるためさらに消費量は増えます。

python

1>>> df = pd.DataFrame({'name': ['john', 'graham', 'elic'], 'age': [25, 30, 22]}, index = ['cleese', 'chapman', 'idle']) 2>>> print(df) 3 name age 4cleese john 25 5chapman graham 30 6idle elic 22 7>>> df.__sizeof__() 8397 9>>> df.index.__sizeof__() 10188 11>>> df.index.values.__sizeof__() 1296 13>>> df.index.values[0].__sizeof__() 1455 15>>> df.index.values[1].__sizeof__() 1656 17>>> df.index.values[2].__sizeof__() 1853 19>>> df.columns.__sizeof__() 20201 21>>> df.columns.values.__sizeof__() 2296 23>>> df.columns.values[0].__sizeof__() 2453 25>>> df.columns.values[1].__sizeof__() 2652 27>>> df.values.__sizeof__() 28112 29>>> df.values[0,0].__sizeof__() 3053 31>>> df.values[0,1].__sizeof__() 3228 33>>> df.values[1,0].__sizeof__() 3455 35>>> df.values[1,1].__sizeof__() 3628 37>>> df.values[2,0].__sizeof__() 3853 39>>> df.values[2,1].__sizeof__() 4028

もしも、以下のように変更してメモリ状況が改善するなら上記の推測が正しい可能性があります。

python

1 if x == "EST": # ESTなら1時間データを早くする 2 new_append(data['time'][i]+timedelta(hours=-1)) 3 else: # EDTならそのまま 4 new_append(data['time'][i])

投稿2021/01/11 16:16

ppaul

総合スコア24670

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

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

mongaa

2021/01/12 10:34

回答ありがとうございます。 ご教授いただいたコードでメモリ状況が改善したため、今回のケースは"原因2"の可能性が高そうです。 今回のケースを自分の言葉で表現してみましたが正しい理解になっていますでしょうか。(教えて頂いた内容の繰り返しになっていて見苦しい部分もあると思います) (1)リスト型変数:newに時間(要素)でなく時間+α(シーケンス)をappendしたために、newに余計な情報が入っている。具体的には【参考】にあるようにリスト内にオブジェクトが代入され、リスト→オブジェクト→時間etcの三層構造になってしまった。 (2)データフレームのvaluesはndarrayシーケンス。その要素がpythonオブジェクト型の場合、オブジェクトがどんな型かによってさらにメモリが消費される。今回のケースでは、変数:newをデータフレーム化した際の要素は【参考】のpythonオブジェクト。よって、既存のコードでは各要素でのメモリ消費が増大、inputのデータ数が多いとその増大量が著しく大きくなってしまった。 【参考】上:newに"シーケンス"を代入(既存のコード) , 下:newに"要素"を代入(ご教授いただいたコード) [time 2019-10-10 06:01:00 Name: 0, dtype: object] [Timestamp('2019-10-10 06:01:00')] いくつか質問してもよろしいでしょうか。 ①"メモリ消費の一例"にあるdf.Indexとdf.valuesにおいて、values[0]~[2]の総和とvaluesが等しくならないのはなぜなのでしょうか? df.Index:55+56+53≠96, df.values:53+28+55+28+53+28≠112. df.columns:96+53+52=201 以下、該当箇所を抜粋 >>> df.index.values.__sizeof__() 96 >>> df.index.values[0].__sizeof__() 55 >>> df.index.values[1].__sizeof__() 56 >>> df.index.values[2].__sizeof__() 53 >>> df.columns.__sizeof__() 201 >>> df.columns.values.__sizeof__() 96 >>> df.columns.values[0].__sizeof__() 53 >>> df.columns.values[1].__sizeof__() 52 >>> df.values.__sizeof__() 112 >>> df.values[0,0].__sizeof__() 53 >>> df.values[0,1].__sizeof__() 28 >>> df.values[1,0].__sizeof__() 55 >>> df.values[1,1].__sizeof__() 28 >>> df.values[2,0].__sizeof__() 53 >>> df.values[2,1].__sizeof__() 28 ②(1),(2)いずれも不必要な余分なオブジェクトを大量に生成したのですが、(2)のタイミングでメモリが急増した理由が理解できていないです。   言い換えると、list内に不必要なオブジェクトが入るよりもデータフレーム内に入った方がメモリ消費が激しい理由を知りたいです。   教えて頂いた内容から推測すると、データフレームではindexやcolumnsもvaluesをもっているため、df.valuesが複雑になると、df.indexやdf.columnsも複雑化し(どう複雑になるのかは説明できないです・・)メモリを食うといった感じでしょうか? かなり乱雑な文章になり申し訳ありませんが、回答いただけると幸いです。
ppaul

2021/01/12 12:50

説明が良くなかったようです。 私の説明はどれかが原因というのではなく、全てが重なり合った結果だという意味です。 また、原因1は推測ではなく、以前ソースを読んで確認済です。 (1)リスト型変数:newに時間(要素)でなく時間+α(シーケンス)をappendしたために、newに余計な情報が入っている。具体的には【参考】にあるようにリスト内にオブジェクトが代入され、リスト→オブジェクト→時間etcの三層構造になってしまった。 はい、その通りです。 (2)データフレームのvaluesはndarrayシーケンス。その要素がpythonオブジェクト型の場合、オブジェクトがどんな型かによってさらにメモリが消費される。今回のケースでは、変数:newをデータフレーム化した際の要素は【参考】のpythonオブジェクト。よって、既存のコードでは各要素でのメモリ消費が増大、inputのデータ数が多いとその増大量が著しく大きくなってしまった。 データフレームのvaluesは二次元のndarrayです。 ndarrayのサイズは、例えばdtypeがint32なら(4×要素数)+96バイトぐらいあり、それだけなのですが、dtypeがオブジェクトの場合はオブジェクト自体は別のところにあるので、そのメモリを別に使っています。 ①"メモリ消費の一例"にあるdf.Indexとdf.valuesにおいて、values[0]~[2]の総和とvaluesが等しくならないのはなぜなのでしょうか? valuesの大きさは、入れ物であるndarrayが消費しているメモリ量だけです。 df.index自体、df.index.values自体、df.index.values[0]~[2]のそれぞれのサイズは188, 96, 55, 56, 53であり、メモリ境界が8バイトなら 192, 96, 56, 56, 56のメモリを消費し、合計で192+96+56+56+56=456バイトのメモリを消費しています。 df全体では、96+55+56+53+201+96+53+52+112+53+28+55+28+53+28=1056バイトのメモリを消費しています。   言い換えると、list内に不必要なオブジェクトが入るよりもデータフレーム内に入った方がメモリ消費が激しい理由を知りたいです。 それが原因4ではないかと推測しています。 pandasのソースを読めば原因は分かるとは思いますが、pandasには852個の.pyファイル、41個の動的リンクライブラリ(C言語ソース、cythonソース)があり、かなりの時間がかかるでしょう。
mongaa

2021/01/12 20:27

すみません。原因1~4全てが関わっている旨、理解しました。 >>また、原因1は推測ではなく、以前ソースを読んで確認済です。 →貴重な情報ありがとうございます。Cpytho自体の問題点について知られてよかったです。 >>dtypeがオブジェクトの場合はオブジェクト自体は別のところにあるので、そのメモリを別に使っています。 →Pyhtonオブジェクトを要素にしている場合そのオブジェクト次第でメモリが決まる、というのは言われると理解できるのですが、あまり考えてコーディングしていなかったです。 今後注意してみようと思います。ありがとうございます。 >>valuesの大きさは、入れ物であるndarrayが消費しているメモリ量だけです。 →"入れ物である"という表現でイメージがつきました。ありがとうございます。 >>それが原因4ではないかと推測しています。pandasのソースを読めば原因は分かるとは思いますが、pandasには852個の.pyファイル、41個の動的リンクライブラリ(C言語ソース、cythonソース)があり、かなりの時間がかかるでしょう。 →データフレームはそういうものだと受け入れて今後は気をつけようと思います。笑 疑問解決できました!!ありがとうございます!
guest

0

for文でenumerateでループさせる必要はないですね。1行でデータフレームそのものを書き換えられます。
以下のコードを参考にSummerを書き換えてはいかがでしょう。

Python

1import pandas as pd 2import io 3from datetime import timedelta 4 5txt = """ 6time,summer 72019-10-10 06:02:00,EDT 82020-11-23 06:02:00,EST 92021-01-01 00:12:34,EST 10""" 11 12df = pd.read_csv(io.StringIO(txt)) 13print(df) 14 15df['time'] = pd.to_datetime(df['time']) 16df.loc[df['summer'] == 'EST', 'time'] += timedelta(hours=-1) 17df['time'] = df['time'].astype(str) 18 19print(df)

result

1 time summer 20 2019-10-10 06:02:00 EDT 31 2020-11-23 06:02:00 EST 42 2021-01-01 00:12:34 EST 5 time summer 60 2019-10-10 06:02:00 EDT 71 2020-11-23 05:02:00 EST 82 2020-12-31 23:12:34 EST

投稿2021/01/11 12:17

編集2021/01/11 12:23
Daregada

総合スコア11990

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

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

mongaa

2021/01/11 15:25

ありがとうございます! inputデータを長くしてもメモリが増大せずfor文を使わないため実行時間もかなり短くなりました! なるべくfor文を使わないように気を付けていましたが、こんなに簡単なコードで書けることに気づけませんでした・・・ 綺麗なコードを教えていただき感謝いたします。 上記コードで対策については十二分なのですが、はじめのコードで急にメモリが増大した理由として考え付くものがありましたら教えていただけると幸いです。 関数:Summer実行中ではなく変数:newをデータフレーム化するタイミングで増大したので理由が全く見当がつかないです。変数をデータフレーム化するだけでメモリが増大するものなのでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問