私がどこから来たのかについて前もっていくつかのコンテキスト。コードスニペットは最後にあります。
可能な場合は、H2Oなどのオープンソースツールを使用して超高性能の並列CSVファイルを読み取ることを好みますが、このツールは機能セットに制限があります。監視付き学習のためにH2Oクラスターにフィードする前に、データサイエンスパイプラインを作成するために多くのコードを作成することになります。
マルチプロセッシングライブラリのプールオブジェクトとマップ関数に多数の並列処理を追加することで、UCIリポジトリから8GB HIGGSデータセットや40GB CSVファイルなどのデータサイエンスファイルを非常に高速に読み込んでいます。たとえば、最近傍検索を使用したクラスタリングと、DBSCANおよびマルコフのクラスタリングアルゴリズムでは、深刻な問題のあるメモリと実時間の問題を回避するために、並列プログラミングの巧妙さが必要です。
私は通常、最初にgnuツールを使用してファイルを行ごとの部分に分割し、次にそれらすべてをglob-filemaskして、Pythonプログラムでそれらを並列に検索して読み取ります。私は1000以上の部分ファイルをよく使用しています。これらのトリックを実行すると、処理速度とメモリ制限が非常に役立ちます。
パンダdataframe.read_csvはシングルスレッドなので、これらのトリックを実行して、並列実行のためにmap()を実行することにより、パンダを非常に高速にすることができます。htopを使用して、単純な古いシーケンシャルパンダdataframe.read_csvを使用すると、1つのコアの100%cpuが、ディスクではなくpd.read_csvの実際のボトルネックであることを確認できます。
高速ビデオカードバスでSSDを使用しており、SATA6バスで回転するHDではなく、16 CPUコアを使用していることを追加する必要があります。
また、一部のアプリケーションでうまく機能することを発見した別の手法は、1つの大きなファイルを多数のパーツファイルに事前分割するのではなく、1つの巨大なファイル内のすべてを並列CSVファイルで読み取り、各ワーカーをファイル内の異なるオフセットで開始することです。各並列ワーカーでpythonのファイルseek()およびtell()を使用して、大きなテキストファイルをストリップで、大きなファイルの異なるバイトオフセットの開始バイトと終了バイトの場所で、同時にすべて読み取ります。バイトに対して正規表現findallを実行し、改行の数を返すことができます。これは部分的な合計です。最後に部分的な合計を合計して、ワーカーが終了した後にmap関数が戻ったときにグローバルな合計を取得します。
以下は、並列バイトオフセットトリックを使用したベンチマークの例です。
私は2つのファイルを使用しています:HIGGS.csvは8 GBです。これは、UCI機械学習リポジトリからのものです。all_bin .csvは40.4 GBで、現在のプロジェクトからのものです。私は2つのプログラムを使用します。Linuxに付属するGNU wcプログラムと、私が開発した純粋なpython fastread.pyプログラムです。
HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv
HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb 2 09:00 all_bin.csv
ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496
real 0m8.920s
user 1m30.056s
sys 2m38.744s
In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175
これは、4.5 GB /秒、つまり45 Gb /秒のファイルスラッピング速度です。回転しているハードディスクではありません、私の友人。これは実際にはSamsung Pro 950 SSDです。
以下は、純粋なCコンパイルプログラムであるgnu wcによって行カウントされる同じファイルの速度ベンチマークです。
この場合、私の純粋なpythonプログラムが、gnu wcでコンパイルされたCプログラムの速度と基本的に一致していることがわかります。Pythonは解釈されますが、Cはコンパイルされるので、これはかなり興味深い速度の偉業です。もちろん、wcは実際には並列プログラムに変更する必要があります。そうすれば、私のpythonプログラムの靴下を打ち負かすことになります。しかし、今日のように、gnu wcは単なるシーケンシャルプログラムです。あなたはあなたができることをします、そしてpythonは今日並行して行うことができます。Cythonのコンパイルが私を助けることができるかもしれません(しばらくの間)。また、メモリマップファイルはまだ調査されていません。
HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv
real 0m8.807s
user 0m1.168s
sys 0m7.636s
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000
real 0m2.257s
user 0m12.088s
sys 0m20.512s
HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv
real 0m1.820s
user 0m0.364s
sys 0m1.456s
結論:速度は、Cプログラムと比較して、純粋なPythonプログラムの方が優れています。ただし、少なくとも行カウントの目的では、Cプログラムよりも純粋なpythonプログラムを使用するだけでは不十分です。通常、この手法は他のファイル処理にも使用できるため、このpythonコードは依然として優れています。
質問:正規表現を1回だけコンパイルしてすべてのワーカーに渡すと、速度が向上しますか?回答:このアプリケーションでは、正規表現のプリコンパイルは役に立ちません。その理由は、すべてのワーカーのプロセスのシリアル化と作成のオーバーヘッドが支配的であるからだと思います。
もう一つ。並列CSVファイルの読み取りは役に立ちますか?ディスクはボトルネックですか、それともCPUですか。Stackoverflowに関するいわゆるトップレートの回答の多くには、ファイルを読み取るのに必要なスレッドは1つだけであるという一般的な開発の知恵が含まれています。彼らは確かですか?
確認してみましょう:
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000
real 0m2.256s
user 0m10.696s
sys 0m19.952s
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000
real 0m17.380s
user 0m11.124s
sys 0m6.272s
そうそうそう。並列ファイル読み取りは非常にうまく機能します。さてさようなら!
PS 知りたい人がいる場合、単一のワーカープロセスを使用しているときにbalanceFactorが2だったらどうなるでしょうか。まあ、それは恐ろしいです:
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000
real 1m37.077s
user 0m12.432s
sys 1m24.700s
fastread.py Pythonプログラムの主要部分:
fileBytes = stat(fileName).st_size # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)
def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'): # counts number of searchChar appearing in the byte range
with open(fileName, 'r') as f:
f.seek(startByte-1) # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
bytes = f.read(endByte - startByte + 1)
cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
return cnt
PartitionDataToWorkersのdefは、通常の順次コードです。他の誰かが並列プログラミングとはどのようなものかについて練習したい場合に備えて、私はそれを省略しました。学習のために、より難しい部分、つまりテスト済みで動作する並列コードを無料で提供しました。
おかげで:ArnoとCliffによるオープンソースのH2Oプロジェクトと、H2Oスタッフによる優れたソフトウェアと教育用ビデオは、上に示したこの純粋なpython高性能パラレルバイトオフセットリーダーにインスピレーションを与えてくれました。H2Oは、Javaを使用して並列ファイルの読み取りを行い、PythonとRプログラムから呼び出し可能で、巨大なCSVファイルの読み取りにおいて、地球上で最も高速です。