Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
352
Help us understand the problem. What is going on with this article?
@hoto17296

Python で大量のファイルを並列で速く読み込む

More than 1 year has passed since last update.

些細なことでも multiprocessing 使うと便利だよ、という小ネタ。

やりたいこと

from glob import glob

files = glob('data/*.csv')

len(files)  # 10000

この 1万件の CSV ファイルを Pandas DataFrame として読み込みたい。

ちなみに検証用のデータは以下のようにして生成した。
(3列 x 10,000行 の CSV ファイル 10,000 個)

import numpy as np
import pandas as pd

row_n = 10000
col_n = 3

columns = [f'col{i}' for i in range(col_n)]
for i in range(10000):
    df = pd.DataFrame(np.random.randn(row_n, col_n), columns=columns)
    df.to_csv(f'data/{i:04}.csv', index=False)

並列化の手法いくつか

今回は

  • 並列化なし (シングルスレッド)
  • マルチスレッド
  • マルチプロセス

で比較した。

asyncio (イベントループ) については、asyncio がファイルの非同期読み込みをサポートしていなかったので試さなかった。 aiofiles などの外部モジュールを使えばできると思うので、誰か試してみてほしい。 [追記] 試してみた → コメントを参照。

並列化なし (シングルスレッド)

並列化を考えずに全てのファイルを読み込もうとするとこうなる。

import pandas as pd

arr = [pd.read_csv(f) for f in files]

マルチプロセス

multiprocessing.Pool を使う。

import pandas as pd
from multiprocessing import Pool

def read_csv(f):
    return pd.read_csv(f)

with Pool() as p:
    arr = p.map(read_csv, files)

p.map には pd.read_csv を直接渡したり lambda を渡したりすることはできないので、 read_csv という関数を定義している。

Pool() で並列数を指定しなかった場合は CPU のコア数と同じ並列数で実行される。

マルチスレッド

multiprocessing.dummy.Pool は threading を使っているとのことなので、これを使ってみる。

import pandas as pd
from multiprocessing.dummy import Pool

with Pool() as p:
    arr = p.map(pd.read_csv, files)

マルチスレッドの場合は pd.read_csv をそのまま渡せる。

結果

4コア CPU のマシンで計測した結果が以下。

読み込み速度
並列化なし (シングルスレッド) 82 sec
マルチスレッド 28 sec
マルチプロセス 22 sec

マルチプロセスが一番速かった。

4並列で読み込んで4倍速くなるとまではいかなかったが、並列化の恩恵を十分に感じられる程度には速くなった。

マルチスレッドの注意点

今回はマルチスレッドもマルチプロセスとほぼ同じくらいには速くなったので、手軽さを重視してマルチスレッドの方を採用してもいいケースもあるかもしれない。

ただし Python (CPython) のマルチスレッドは GIL (Global Interpreter Lock) の制約があり、複数のスレッドが同時に Python バイトコードを実行することができない。

そのため、今回のファイル読み込みのような I/O バウンドな処理は高速化できるかもしれないが、例えば「読み込んだ各データに対してなんらかの重い処理を行う」などの CPU バウンドな処理を併せて実行した場合はおそらく今回ほど速くならない。そのようなケースではマルチプロセスで並列化した方が高速化できると思われる。

違う気がしてきた・・・。
詳しくはコメントを参照。

arr = [open(f).read() for f in files]

Pandas のパース処理を入れずに単純にファイル内容を読み込むだけにすると 3.2 秒で終わるので、今回の処理は I/O バウンドではなく CPU バウンドだった・・・。

352
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
hoto17296
ソフトウェアエンジニアです
churadata
沖縄で データ分析 / 機械学習 / Deep Learning をやっている会社です

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
352
Help us understand the problem. What is going on with this article?