私は通常、最大20 Gbのサイズのテキストファイルを使用し、特定のファイルの行数を非常に頻繁にカウントしていることに気付きます。
今のやり方はcat fname | wc -l
で、非常に時間がかかります。もっと速くなるソリューションはありますか?
私は、Hadoopがインストールされた高性能クラスターで働いています。マップ削減アプローチが役立つかどうか疑問に思っていました。
wc -l
ソリューションのように、1行実行するだけの簡単なソリューションにしたいのですが、それがどの程度実現可能かはわかりません。
何か案は?
試してください:sed -n '$=' filename
また、catは不要です。現在の方法では、wc -l filename
で十分です。
制限速度係数はストレージデバイスのI/O速度であるため、単純な改行/パターンカウントプログラム間の変更は役に立ちません。これらのプログラム間の実行速度の差は、ディスク/ストレージ/あなたが持っているものは何でも。
ただし、ディスク/デバイス間で同じファイルをコピーした場合、またはファイルがそれらのディスクに分散されている場合、操作を確実に並行して実行できます。このHadoopについて具体的には知りませんが、4つの異なる場所から10 GBのファイルを読み取ることができると仮定すると、ファイルの一部で4つの異なる行カウントプロセスを実行し、その結果を合計できます。
$ dd bs=4k count=655360 if=/path/to/copy/on/disk/1/file | wc -l &
$ dd bs=4k skip=655360 count=655360 if=/path/to/copy/on/disk/2/file | wc -l &
$ dd bs=4k skip=1310720 count=655360 if=/path/to/copy/on/disk/3/file | wc -l &
$ dd bs=4k skip=1966080 if=/path/to/copy/on/disk/4/file | wc -l &
各コマンドラインで&
に注意してください。したがって、すべてが並行して実行されます。 dd
はcat
と同様に機能しますが、読み込むバイト数(count * bs
バイト)と入力の先頭でスキップするバイト数(skip * bs
バイト)を指定できます)。ブロックで機能するため、bs
をブロックサイズとして指定する必要があります。この例では、10Gbファイルを4Kbの4つの等しいチャンクに分割しました* 655360 = 2684354560バイト= 2.5GB、各ジョブに1つ、サイズに応じてスクリプトを設定することができますファイルと実行する並列ジョブの数。また、実行の結果を合計する必要があります。これは、シェルスクリプト機能が不足しているために行ったことではありません。
ファイルシステムが、RAIDや分散ファイルシステムなどのように大きなファイルを多くのデバイスに分割し、並列化できるI/O要求を自動的に並列化するのに十分なスマートであれば、そのような分割を行い、多くの並列ジョブを実行できますが、同じファイルパスであり、速度がいくらか向上する場合があります。
編集:私が思いついた別のアイデアは、ファイル内の行が同じサイズである場合、ファイルのサイズを行のサイズでバイト単位で割ることで正確な行数を取得できるということです。 1つのジョブでほぼ瞬時に実行できます。平均サイズがあり、行数を正確に気にしないが、推定が必要な場合、この同じ操作を実行し、正確な操作よりもはるかに高速で満足のいく結果を得ることができます。
マルチコアサーバーでは、 GNU parallel を使用して、ファイルの行を並列にカウントします。各ファイルの行数が出力された後、bcはすべての行数を合計します。
find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc
スペースを節約するために、すべてのファイルを圧縮したままにすることもできます。次の行は、各ファイルを解凍し、その行を並行してカウントし、すべてのカウントを合計します。
find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc
データがHDFSにある場合、おそらく最速のアプローチはhadoopストリーミングを使用することです。 Apache PigのCOUNT UDFはバッグで動作するため、単一のレデューサーを使用して行数を計算します。代わりに、次のように単純なhadoopストリーミングスクリプトでレデューサーの数を手動で設定できます。
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar -Dmapred.reduce.tasks=100 -input <input_path> -output <output_path> -mapper /bin/cat -reducer "wc -l"
減速機の数を手動で100に設定しましたが、このパラメーターは調整できます。 map-reduceジョブが完了すると、各レデューサーからの結果は個別のファイルに保存されます。行の最終カウントは、すべてのレデューサーによって返される数値の合計です。次のようにして最終的な行数を取得できます。
$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc
私のテストによると、Spark-Shell(Scalaベース)が他のツール(GREP、SED、AWK、Perl、WC)よりもはるかに高速であることを確認できます。これは、23782409行のファイルで実行したテストの結果です
time grep -c $ my_file.txt;
実数0m44.96sユーザー0m41.59s sys 0m3.09s
time wc -l my_file.txt;
実数0m37.57sユーザー0m33.48s sys 0m3.97s
time sed -n '$=' my_file.txt;
実数0m38.22sユーザー0m28.05s sys 0m10.14s
time Perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt
;
実数0m23.38sユーザー0m20.19s sys 0m3.11s
time awk 'END { print NR }' my_file.txt;
実数0m19.90sユーザー0m16.76s sys 0m3.12s
spark-Shell
import org.joda.time._
val t_start = DateTime.now()
sc.textFile("file://my_file.txt").count()
val t_end = DateTime.now()
new Period(t_start, t_end).toStandardSeconds()
res1:org.joda.time.Seconds = PT15S
質問はもう数年前ですが、 Ivellaの最後のアイデア 、このbashスクリプトestimates1行のサイズを測定し、そこから外挿することにより、数秒以内に大きなファイルの行数をカウントします。
#!/bin/bash
head -2 $1 | tail -1 > $1_oneline
filesize=$(du -b $1 | cut -f -1)
linesize=$(du -b $1_oneline | cut -f -1)
rm $1_oneline
echo $(expr $filesize / $linesize)
このスクリプトにlines.sh
という名前を付けると、lines.sh bigfile.txt
を呼び出して、推定行数を取得できます。私の場合(約6 GB、データベースからエクスポート)、実際の行数からの偏差はわずか3%でしたが、実行速度は約1000倍でした。ちなみに、最初の行には列名があり、実際のデータは2行目から始まるため、最初ではなく2行目を基準として使用しました。
Hadoopは本質的に、@ Ivellaが提案していることと同様のことを実行するメカニズムを提供しています。
HadoopのHDFS(分散ファイルシステム)は、20 GBのファイルを取得し、固定サイズのブロックでクラスター全体に保存します。ブロックサイズを128MBに設定するとします。ファイルは20x8x128MBブロックに分割されます。
次に、このデータに対してmap reduceプログラムを実行し、基本的に(mapステージで)各ブロックの行をカウントしてから、これらのブロック行カウントをファイル全体の最終行カウントに減らします。
パフォーマンスに関しては、一般にクラスターが大きいほどパフォーマンスは向上します(より多くのwcがより多くの独立したディスク上で並行して実行されます)が、ジョブオーケストレーションにはオーバーヘッドがあります。ローカルwcを実行するよりもスループット
pythonの方が速いかどうかわかりません:
[root@myserver scripts]# time python -c "print len(open('mybigfile.txt').read().split('\n'))"
644306
real 0m0.310s
user 0m0.176s
sys 0m0.132s
[root@myserver scripts]# time cat mybigfile.txt | wc -l
644305
real 0m0.048s
user 0m0.017s
sys 0m0.074s
ボトルネックがディスクである場合、それをどのように読み取るかが重要です。 dd if=filename bs=128M | wc -l
は、HDDと高速のCPUおよびRAMを搭載したマシンの場合、lotwc -l filename
またはcat filename | wc -l
よりも高速です。ブロックサイズを試して、dd
がスループットとして報告するものを確認できます。 1GiBまで上げました。
注:cat
とdd
のどちらが速いかについては、いくつかの議論があります。私が主張しているのは、dd
はシステムによっては高速になる可能性があり、それが私のためだということです。自分で試してみてください。
コンピューターにpythonがある場合は、シェルからこれを試すことができます。
python -c "print len(open('test.txt').read().split('\n'))"
これはpython -c
を使用してコマンドを渡します。コマンドは基本的にファイルを読み取り、「改行」で分割して、改行の数またはファイルの全長を取得します。
bash-3.2$ sed -n '$=' test.txt
519
上記を使用して:
bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519
find -type f -name "filepattern_2015_07 _ *。txt" -exec ls -1 {} \; |猫| awk '// {print $ 0、system( "cat" $ 0 "|" "wc -l")}'
出力:
仮定してみましょう:
次に、ファイルをパーツに分割し、複数のノードで並行してパーツをカウントし、そこから結果を合計します(これは基本的に@Chris Whiteのアイデアです)。
GNU Parallel(バージョン> 20161222)でこれを行う方法は次のとおりです。 ~/.parallel/my_cluster_hosts
にノードをリストする必要があり、それらすべてにssh
アクセス権が必要です。
parwc() {
# Usage:
# parwc -l file
# Give one chunck per Host
chunks=$(cat ~/.parallel/my_cluster_hosts|wc -l)
# Build commands that take a chunk each and do 'wc' on that
# ("map")
parallel -j $chunks --block -1 --pipepart -a "$2" -vv --dryrun wc "$1" |
# For each command
# log into a cluster Host
# cd to current working dir
# execute the command
parallel -j0 --slf my_cluster_hosts --wd . |
# Sum up the number of lines
# ("reduce")
Perl -ne '$sum += $_; END { print $sum,"\n" }'
}
使用:
parwc -l myfile
parwc -w myfile
parwc -c myfile