こんにちは! プログラマーのakiraです。
Pythonでアプリケーションを快適に動作させるためには、メモリの操作・開放についての知識は必要になります。
メモリ解放ってどうやってやるんだろう?
どうやってメモリを効率的に使用すればいいのだろう?
と疑問に思ったことはないでしょうか?そんな方に向けて、基礎からメモリの開放や効率的に使う方法について以下の内容で解説していきます。
- 【基礎】メモリの基礎
- 【実践】Pythonでメモリを開放する方法
- 【実践】メモリを効率的に使う方法
本記事ではPythonでメモリを扱う方法について、初心者でもわかりやすく解説していますので、ぜひ参考にしてください!
本記事を読む前に、Pythonがどんなプログラミング言語なのかをおさらいしておきたい人は次の記事を参考にしてください。
→ Pythonとは?特徴やできること、活用例をわかりやすく簡単に解説
メモリについて考えてみよう!
メモリとは
メモリとはPC上でデータを一時的に記憶しておく場所です。変数などに格納したデータを高速に処理する必要があるプログラミング処理ではメモリとうまく付き合っていくことが重要になるのです!
それではこれからプログラミングとメモリの関係について詳しく見ていきましょう!
ガベージコレクションとは
ガベージコレクションとはあるプログラムで確保したメモリの内、不要になったメモリを自動的に解放してくれる機能になります。プログラミングをしていると変数にデータを格納したり、ファイルからデータを読みこんだりして、メモリ上に必要なデータを読み込んで、様々な処理を実行します!
しかしメモリへ読み込んだデータは必要な処理が完了したら、その後の処理では必要のないゴミデータとなってしまいますよね?そこでPythonなどの言語では、Garbage Collector(ごみを集める人)が登場するわけです。PCなどのメモリは無限に使えるわけではなく、数ギガバイトなどの限界があると思います。
このガベージコレクタが不要になったゴミデータをメモリから開放してくれることで、メモリ上にゴミデータが溢れてしまうことを防ぐのです!
メモリリークとは
「ガベージコレクションとは」で、ガベージコレクタが不要になったゴミデータをメモリから開放してくれる役割を担っていることを説明しました。それでは、メモリ上にゴミデータが溢れてしまった場合はどうなるのでしょうか?メモリの使用可能な容量がどんどん減っていってしまいますよね?
この使用可能なメモリ領域がどんどん減っていき、PCやサーバなどの不具合を招くバグをメモリリークというのです!
Pythonでメモリ解放の方法を確認しよう!
これまで説明してきたようにPythonにはGC(ガベージコレクション)の機能があるため、メモリ開放はC言語などのように手動で実施することなく、大抵はGCが自動で行ってくれます。そのため普段はメモリについて大きく意識することなくプログラミングの処理を書くことに専念できるのです!
しかし大量のデータを扱う場合やメモリ制限のある環境では、GCの判断で開放を行うのではなく、必要なくなったタイミングで即座に開放したい場合も出てきます。次項でPythonのメモリを手動で開放する方法について見ていきましょう!
delで要素を削除してみよう!
それではdelで要素を削除する方法について見ていきましょう。以下のようにすることで、delで要素を削除することができます。
del 要素
それでは次のサンプルコードを見ていきましょう!
delでdel_testを削除後、del_testが参照できなくなっていることがわかるかと思います!
del_test = ["memory del test"] * 10 print(del_test) del del_test print(del_test)
実行結果
['memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test', 'memory del test'] Traceback (most recent call last): File "del.py", line 6, in <module> print(del_test) NameError: name 'del_test' is not defined
gc.collectでメモリ解放してみよう!
それでは、メモリを開放する方法について見ていきましょう!
まずdelで解放したい要素を削除し、gc.collect()でメモリを強制的に開放することにより、メモリを再利用することができるようになります!
import gc gc_test = ["memory del test"] * 10 del gc_test gc.collect()
メモリを効率的に使う方法を検討してみよう!
今度はメモリを効率的に使う方法について考えていきましょう。プログラミングでメモリ問題を引き起こすものの一つとして、巨大なファイルの読み込みがあります。メモリに乗らないような数十GBの巨大なファイルを一気に開きメモリが足りなくなってしまうと、メモリリークなどの不具合を引き起こしてしまうことがあります!
最悪の場合、サーバ上の処理全体が停止し、サービス止まってしまう場合もあるのです。Pythonでメモリを効率的に使用する方法をマスターして、メモリエラーを未然に防げるようにしましょう!
それでは、次項以降で読み込みに使用するサンプルのCSVを以下のコードより作成しておいてください!
l = [] for i in range(100): l.append(str(i) + ',sample,csv') with open('sample.csv', 'w') as f: f.write('n'.join(l))
以下のような内容が記載されたCSVファイルが作成されます!
sample.csv
0,sample,csv 1,sample,csv 2,sample,csv . . 98,sample,csv 99,sample,csv
yieldを使う
それでは、yieldを使用してメモリを効率的に使う方法を考えていきましょう!
yieldとは処理を一時的に停止させて値を返すことができる機能です。またこのyieldを使用するとジェネレータという反復可能なオブジェクトを作ることができます!
yieldやジェネレータって何?という方はこちらの記事を見てください!
では、サンプルコードを見ていきます!
関数file_generatorではファイルを渡すとファイルの中身を一行ずつ返してくれるジェネレーターを生成します。実行結果は、print(next(gen))でsample.csvの1、2、3行目を表示しています!
def file_generator(file): with open(file, encoding="utf-8") as f: for line in f: yield line file_path = 'sample.csv' gen = file_generator(file_path) print(next(gen)) print(next(gen)) print(next(gen))
実行結果
0,sample,csv 1,sample,csv 2,sample,csv
このようにyieldを使用してファイルの中身を一行ずつ返すジェネレーターを作成することによって、ファイル全体をメモリ上に読み込む必要がなくなるのです!
次は作成したジェネレーターをfor文でループしてみましょう!
ファイルの中身を一行ずつ取得し、全行表示することができます!
def file_generator(file): with open(file, encoding="utf-8") as f: for line in f: yield line file_path = 'sample.csv' gen = file_generator(file_path) for line in gen: print(line)
実行結果
0,sample,csv 1,sample,csv 2,sample,csv . . . 97,sample,csv 98,sample,csv 99,sample,csv
pandasでchunksizeを指定する
次は、pandasを使用してメモリを効率的に使う方法を考えていきましょう。pandasとはデータを効率的に処理できるPythonのデータ分析ライブラリです。
pandasって何?という方は、以下のページに詳しく解説されています!
またpandasのread_csvでCSVを扱う方法は、以下のページに解説されています!
それではサンプルコードを見ていきましょう。pandasは、csvファイルを読み込む際にchunksizeという一度にメモリ上に読み込む行数を指定できます。今回は、chunksizeを10に指定しているため一度に10行ずつ読み込む事かできます!
import pandas as pd reader = pd.read_csv('sample.csv', encoding='utf-8', chunksize=10, header=None) print(next(reader)) print(next(reader))
実行結果
0 1 2 0 0 sample csv 1 1 sample csv 2 2 sample csv 3 3 sample csv 4 4 sample csv 5 5 sample csv 6 6 sample csv 7 7 sample csv 8 8 sample csv 9 9 sample csv 0 1 2 10 10 sample csv 11 11 sample csv 12 12 sample csv 13 13 sample csv 14 14 sample csv 15 15 sample csv 16 16 sample csv 17 17 sample csv 18 18 sample csv 19 19 sample csv
print(next(reader))を2回実行しているため、10行ずつ0~9、10〜19行を読み込むことができていますね。次のように全行取得したい場合は、for文でループすることにより10行ずつ全行を取得することができます!
import pandas as pd reader = pd.read_csv('sample.csv', encoding='utf-8', chunksize=10, header=None) for i in reader: print(i)
実行結果
0 1 2 0 0 sample csv 1 1 sample csv 2 2 sample csv 3 3 sample csv 4 4 sample csv 5 5 sample csv 6 6 sample csv 7 7 sample csv 8 8 sample csv 9 9 sample csv 0 1 2 10 10 sample csv 11 11 sample csv 12 12 sample csv . . . 87 87 sample csv 88 88 sample csv 89 89 sample csv 0 1 2 90 90 sample csv 91 91 sample csv 92 92 sample csv 93 93 sample csv 94 94 sample csv 95 95 sample csv 96 96 sample csv 97 97 sample csv 98 98 sample csv 99 99 sample csv
daskを使用する
今度はdaskを使用した効率化の方法を考えていきましょう!
daskとは柔軟な並列計算を行うライブラリです。つまり、daskではメモリに乗らないようなファイルでもdask側で調整して分散処理を行ってくれるため、巨大なファイルも扱うことができるようになります。メモリ上に読み込む量もdask側で調整してくれるため柔軟な処理が可能となります!
それでは、daskを使用してサンプルのCSVファイルを読み込んでみましょう!
import dask.dataframe as dd reader = dd.read_csv('sample.csv', encoding='utf-8', header=None) print(reader.compute())
実行結果
0 1 2 0 0 sample csv 1 1 sample csv 2 2 sample csv . . . 98 98 sample csv 99 99 sample csv [100 rows x 3 columns]
このようにdaskが調整してファイルを読み込み、分散処理をしてくれるため巨大なファイルも高速に扱うことができるのです!
まとめ
いかがでしたでしょうか。今回は、Pythonのメモリについて学習しました!
メモリについて考えたり、メモリを意識したプログラミングをすることは、初級から中級プログラマにステップアップする上でも大切なことですので、しっかり理解して活用できるようにしていきましょう!