86006


特定のディレクトリにある大量のJSONファイルを読んで  
それを加工してCSVに吐くようなバッチ処理があります

中の処理は JSON を読んだら何行かレコードを出力して  
その後JSON自体は捨ててしまっていいはずで  
実行途中にメモリ上に置く必要があるのはJSON１個分のはずで  
JSONファイル１つ１つは大きくて 2MB でほとんどは数KBなのですが  
このプログラムがなぜか 3GB のメモリを消費します

しかも全体で３０分ぐらいかかるバッチなんですが  
実行中だんだんメモリが増えていってしまいます

概略でかくとこんな感じです

[code]

    with open('xxx.csv', 'w') as wf:
      writer = csv.writer(wf)
    
      for json_file in directory_files:
        with open(json_file) as rf:
          json_data = rf.read()
          data = json.loads(json_data)
    
          csv_line = [data['key1'], data['key2'], ...] 
          # 実際はもっと複雑なことをしてますが data に対しては read only な処理です
    
          writer.writerow(csv_line)
    
          del data
          del json_data
          gc.collect()
    
[/code]

他に例外処理とか終了通知とかログとかいろいろいれてるんですがファイル数に対して増える処理はこれだけです

  1. del data を読んだ場合ネストしたハッシュの内側も全て削除してくれるのでしょうか

  2. ソースコード上のどの変数でメモリが増えてるか調べる方法はないでしょうか

* * *

追記：

flush したり こまめに close open するのは効果なかったです

@profile をつけて行ごとのメモリ使用量を出力しながら実行したんですが  
遅すぎて１GBとかまでいくまで確認できなかったんですが  
最初２００MBぐらいからはじまって３００MBぐらいまでの間では  
range(...) とか json.load とか writerows の中でときどきメモリが 0.1 とか 0.2
とか増えて定期的に一気に減ったりするので一時データやIOバッファの確保にメモリが使われてるだけみたいでした


CC BY-SA 4.0
2022-01-28T10:11:06.270
85985
2022-02-08T05:18:18.270
2022-02-08T03:45:53.680
question
python
python でオブジェクトが解放されたか確認する方法


表題の質問に対する回答にはなりませんが、

> 実行途中にメモリ上に置く必要があるのはJSON１個分のはずで JSONファイル１つ１つは大きくて 2MB でほとんどは数KBなのですが  
>  このプログラムがなぜか 3GB のメモリを消費します しかも全体で３０分ぐらいかかるバッチなんですが  
>  実行中だんだんメモリが増えていってしまいます

これについて言えば、`wf` を open し続けているので、大量の JSON ファイルを書き出す総量分に相当するメモリーを占拠し続けるのではないですか？

[code]

    with open('xxx.csv', 'w') as wf:
        writer = csv.writer(wf)
    
[/code]

この部分を for ブロックの外側ではなくて、for の内側で **追記モードで** 都度、オープン、書き出し、クローズするようにしたらどうでしょうか？

[code]

    for json_file in directory_files:
        with open(json_file) as rf, open('xxx.csv', 'a') as wf:
            ...
            writer = csv.writer(wf)
            writer.writerow(csv_line)
            ...
    
[/code]

追記モードでもファイル全体と同じサイズのメモリーを消費するのだとしたら、結局大差ないかもしれませんが……。（オープンクローズの繰り返しにより、却って GC
がより頻発に発生するだけかも？）


CC BY-SA 4.0
2022-01-28T14:51:02.647
85986
2022-01-28T14:51:02.647
7290
85985
answer


CSVファイルの出力がメモリにバッファリングされている気がします。  
フラッシュしてはいかがでしょうか。

[code]

    wf.flush()
    
[/code]


CC BY-SA 4.0
2022-01-29T06:32:49.393
85994
2022-01-29T06:32:49.393
35558
85985
answer


質問に書いてあるコードにはメモリ使用量を増やす要素は見当たりません。  
確認するコードを書いてみました。

[code]

    import csv
    import json
    
    NUMBER_OF_FILES = 10_000
    NUMBER_OF_COLUMNS = 10
    
    directory_files = [f'{i}.json' for i in range(NUMBER_OF_FILES)]
    
    for f in directory_files:
        with open(f, 'w') as w:
            json.dump({f'key{k}': random.randint(0, 1000000) for k in range(NUMBER_OF_COLUMNS)}, w)
    
    with open('xxx.csv', 'w') as wf:
        writer = csv.writer(wf)
    
        for json_file in directory_files:
            with open(json_file) as rf:
                json_data = rf.read()
                data = json.loads(json_data)
    
                csv_line = [data[f'key{i}'] for i in range(NUMBER_OF_COLUMNS)]
    
                writer.writerow(csv_line)
    
[/code]

ここで`NUMBER_OF_FILES`や`NUMBER_OF_COLUMNS`を **変えても、メモリ使用量が増えていくようなことは起こりません** 。  
確認してみてください。  
（こちらで確認したのは Python 3.8.12 macOS です）

質問に書かなかったコードの側に使用量が増える原因（オブジェクトを参照したままにするような箇所）があるのでしょう。

* * *

(追記)

>   1. del data を読んだ場合ネストしたハッシュの内側も全て削除してくれるのでしょうか
>

そもそもdelは「名前をオブジェクトから解放する」文であって、オブジェクトを解放する文ではありません。前提が間違っているように思います。

>   2. ソースコード上のどの変数でメモリが増えてるか調べる方法はないでしょうか
>

例えば

[code]

    from sys import getsizeof
    from collections.abc import Mapping, Container
    
    def deep_getsizeof(o, ids):
        """Find the memory footprint of a Python object
    
        This is a recursive function that drills down a Python object graph
        like a dictionary holding nested dictionaries with lists of lists
        and tuples and sets.
    
        The sys.getsizeof function does a shallow size of only. It counts each
        object inside a container as pointer only regardless of how big it
        really is.
    
        :param o: the object
        :param ids:
        :return:
        """
        d = deep_getsizeof
        if id(o) in ids:
            return 0
    
        r = getsizeof(o)
        ids.add(id(o))
    
        if isinstance(o, str) or isinstance(o, bytes):
            return r
    
        if isinstance(o, Mapping):
            return r + sum(d(k, ids) + d(v, ids) for k, v in o.items())
    
        if isinstance(o, Container):
            return r + sum(d(x, ids) for x in o)
    
        return r
    
[/code]

みたいな関数を書いて、  
`print(deep_getsizeof([1,2,3,4], set())) # => 200`  
`print(deep_getsizeof([1,1,1,1], set())) # => 116`  
みたいに使えます。

（<https://code.tutsplus.com/tutorials/understand-how-much-memory-your-python-
objects-use--cms-25609> のコードをPython3向けに書き直したもの）


CC BY-SA 4.0
2022-01-29T15:09:55.440
86006
2022-02-08T05:18:18.270
2022-02-08T05:18:18.270
12274
12274
85985
answer