5


4

*問題:*私は巨大な生のテキストファイル(3gigを想定)を持っています。ファイルの各単語を調べて、その単語がファイルに何回現れるかを調べる必要があります。

*私が提案した解決策:*巨大ファイルを複数のファイルに分割すると、分割された各ファイルにはソートされた方法で単語が付きます。 たとえば、「_ _ 」で始まるすべての単語は「 .dic」ファイルに格納されます。 だから、いつでも私たちは26以上のファイルを実行しません。

このアプローチの問題は、

ファイルを読み取るためにストリームを使用できますが、ファイルの特定の部分を読み取るためにスレッドを使用したいと思いました。 たとえば、別のスレッドで0〜1024バイトを読み取ります(最低でも4〜8のスレッドがあります。 プロセッサの数がボックスに存在します。 これは可能ですか、私は夢を見ていますか?

もっと良い方法はありますか?

注:これは純粋なcまたはcベースのソリューションでなければなりません。 データベースなどは許可されていません。

10 Answer


15


KernighanとPikeによる「http://plan9.bell-labs.com/cm/cs/tpop/ [The Practice of Programming]」、特に第3章を参照する必要があります。

C +では、文字列とカウントに基づいたマップを使用します( ` std

map +`、IIRC)。 ファイルを読んで(一度 - 二度以上読むには大きすぎる)、( 'word’の定義のために)行くにつれてそれを単語に分割し、見つけたそれぞれの単語についてマップエントリのカウントを増やします。

Cでは、自分でマップを作成する必要があります。 (あるいはDavid Hansonの "http://www.cs.princeton.edu/software/cii/ [Cインタフェースと実装]"を見てください。)

あるいは、Perl、Python、またはAwk(これらはすべて連想配列を持ち、マップと同等です)を使用できます。


6


ファイルの一部を並行して読み取る複数のスレッドを使用してもそれほど役に立ちません。 このアプリケーションは実際の単語数ではなく、あなたのハードディスクの帯域幅と待ち時間に縛られていると私は思います。 「準ランダム」ファイルアクセスは通常「線形ファイル」アクセスより遅いため、このようなマルチスレッドバージョンは実際にはパフォーマンスが低下する可能性があります。

CPUがシングルスレッドバージョンで本当にビジーである場合は、スピードが上がる可能性があります。 1つのスレッドが大きな塊でデータを読み取り、それらを容量の限られたキューに入れることができます。 他の多くのワーカースレッドがそれぞれ独自のチャンクを操作して単語を数えることができます。 カウントワーカースレッドが終了したら、単語カウンタをマージする必要があります。


3


まず、単語を保存するためのデータ構造を決めます。

明らかな選択は地図です。 しかし、おそらく Trieの方があなたに役立つでしょう。 各ノードで、単語の数を保存します。 0は単語の一部に過ぎないことを意味します。 ストリームを使ってトライベースに挿入し、ファイルを文字ベースで読み込むことができます。

第二 - マルチスレッドはいかいいえ? これは答えるのは簡単ではありません。 データ構造が大きくなるサイズと、答えをどのように並列化するかによって異なります。

  1. シングルスレッド - 簡単で実装が簡単です。

  2. 複数のリーダースレッドと1つのデータ構造を持つマルチスレッド。 その後、データ構造へのアクセスを同期させる必要があります。 トライでは、あなたが実際にいるノードをロックするだけでよいので、複数のリーダーが大きな干渉なしにデータ構造にアクセスすることができます。 特にバランスを取り戻すとき、自己バランスツリーは異なる場合があります。

  3. それぞれが独自のデータ構造を持つ複数のリーダースレッドを持つマルチスレッド。 各スレッドはファイルの一部を読みながらそれ自身のデータ構造を構築します。 それぞれが終わったら、結果を組み合わせる必要があります(これは簡単なはずです)。

考えなければならないことが1つあります。開始するには、スレッドごとに単語の境界を見つける必要がありますが、それが大きな問題になることはありません(例: 各スレッドは最初の単語の境界まで開始し、そこから開始します。最後に、各スレッドは作業中の単語を終了します。


1


あなたがそれを読んだ後にデータを分析するために2番目のスレッドを使うことができる間、あなたはそうすることによって莫大な量を得るつもりはないでしょう。 データの読み取りに複数のスレッドを使用しようとすると、速度が向上するのではなく、ほぼ確実に速度が低下します。 複数のスレッドを使用してデータを処理するのは無意味です - 処理は読み取りよりも何倍も速くなります。そのため、スレッドが1つしかなくても、制限はディスク速度になります。

かなりの速度を得るための1つの(可能な)方法は通常の入出力ストリームを迂回することです - C FILE *を使うのと同じくらい速いものもありますが、本当に速いものは何も知らないし、かなり遅いものもあります。 システム上でこれを実行している場合(例: これは、Cとは明らかに異なるI / Oモデルを持っています。少しの注意でかなり多くのものを得ることができます。

問題は非常に簡単です:あなたが読んでいるファイルはあなたが利用可能なキャッシュスペースより(潜在的に)大きいです - しかしあなたは再びファイルのチャンクを読み直すつもりはないのであなたはキャッシングから何も得ません。少なくともあなたが賢明に物事をやれば)。 そのため、キャッシュを回避し、ディスクドライブから処理可能なメモリにできるだけ直接データを転送するようにシステムに指示します。 Unixライクなシステムでは、これはおそらく `+ open()`と ` read()`です(そして、多くを得ることができません)。 Windowsでは、これは ` CreateFile `と ` ReadFile `であり、 ` FILE_FLAG_NO_BUFFERING `フラグを ` CreateFile +`に渡します。正しく実行すると、おそらく速度が約2倍になります。

また、さまざまな並列構成を使用して処理を行うことを提唱するいくつかの回答も得ました。 これらは根本的に間違っていると思います。 あなたが恐ろしいことをしない限り、ファイル内の単語を数える時間は単にファイルを読むのにかかる時間よりほんの数ミリ秒長くなります。

私が使用する構造は、たとえば1メガバイトあたり2つのバッファを持つことです。 データを1つのバッファに読み込みます。 そのバッファの単語を数えるためにあなたのカウントスレッドにそのバッファをひっくり返しなさい。 それが起こっている間に、2番目のバッファにデータを読み込みます。 これらが完了したら、基本的にバッファを交換して続行します。 あるバッファから次のバッファへと境界をまたがる可能性がある単語を処理するためにバッファを交換する際に必要な余分な処理が少しありますが、それはかなり簡単です(基本的にバッファが白で終わらない場合スペース、あなたがまだデータの次のバッファを操作し始めるときあなたはまだ一言でいます。

それがマルチプロセッサ(マルチコア)マシン上でのみ使用されると確信している限り、本物のスレッドを使用することは問題ありません。 これがシングルコアマシンで行われる可能性がある場合は、代わりに、オーバーラップしたI / Oを持つシングルスレッドを使用することをお勧めします。


1


他の人が指摘したように、ボトルネックはディスクI / Oになります。 したがって、重複したI / Oを使用することをお勧めします。 これは基本的にプログラムロジックを逆にします。 I / Oを実行するタイミングを決定するためのコードのタイリングの代わりに、単純にI / Oが終了したらコードを呼び出すようにオペレーティングシステムに指示します。 I / O補完ポートを使用する場合は、ファイルチャンクの処理に複数のスレッドを使用するようにOSに指示することもできます。


0


Cベースのソリューション?

私はperlがこの目的のために生まれたと思います。


0


ストリームにはカーソルが1つだけあります。 一度に複数のスレッドでストリームにアクセスした場合、目的の場所を確実に読み取ることはできません。 カーソル位置から読み取ります。

私がやることは、ストリームを読み込むスレッドを1つだけ(おそらくメインスレッド)にして、読み込んだバイトを他のスレッドに送ることです。

例では:

  • スレッド#iの準備が整いました。メインスレッドに次の部分を渡すよう依頼します。

  • メインスレッドは次の1Mbを読み、それらをスレッド1に渡します。

  • スレッド#iは1Mbを読み、あなたが望むように単語を数えます、

  • スレッド#iは作業を終了し、次の1Mbをもう一度要求します。

このようにして、ストリームの読み取りとストリームの分析を分けることができます。


0


あなたが探しているのはRegExです。 正規表現エンジン上のこのStackoverflowスレッドは助けになるでしょう:

https://stackoverflow.com/questions/181624/c-what-regex-library-should-i-use[C:どの正規表現ライブラリを使用すればいいですか?]


0


まず、C / Cがこれを処理する最良の方法ではないと確信しています。 理想的には、並列処理にもmap / reduceを使用します。

しかし、あなたの制約を想定して、これが私がすることです。

1)テキストファイルを小さな塊に分割します。 あなたは単語の頭文字でこれをする必要はありません。 それらを5000語のまとまりに分割するだけです。 擬似コードでは、次のようにします。

index = 0

numwords = 0

mysplitfile = openfile(index-split.txt)

while(bigfile >> word)

mysplitfile << word

数字語

if(numwords> 5000)

mysplitfile.close()

索引

mysplitfile = openfile(index-split.txt)

2)共有マップデータ構造とpthreadを使用して、各サブファイルを読み取るための新しいスレッドを生成します。 また、疑似コード:

maplock = create_pthread_lock()

sharedmap = std

map()

index-split.txtファイルごとに、

spawn-new-thread(myfunction、ファイル名、共有マップ、ロック)

dump_map(共有マップ)

void myfunction(filename、sharedmap)\ {

localmap = std :: map();

file = openfile(filename)

while(ファイル>> word)

if!localmap.contains(word)localmap [word] = 0

localmap [word]

キーの取得(ロック)、ローカルマップの値!if sharedmap.contains(key)sharedmap [key] = 0

sharedmap [キー] =値解放(ロック)

}

構文がすみません。 私は最近たくさんのpythonを書いています。


0


Cじゃなくて、ちょっと醜いのですが、たった2分で終了しました。

`+ perl -lane '$ h {$ _} ++ for @F; END {for $ w(sort {$ h {$ b} <⇒ $ h {$ a} || $ a cmp $ b}キー%h){print "$ h {$ w} \ t $ w"} } 'ファイル> freq + `

+ -n +`で各行をループ+ `+ -a +`で各行を `+ @ F +`単語に分割+各 `+ $ _ +`単語はハッシュ `+%h +`をインクリメント+ `+ END +`が1回の `+ file +`に到達しました、+ `+ sort +`頻度によるハッシュ `+ $ h {$ b} <⇒ $ h {$ a} + + 2つの頻度が同じ場合、アルファベット順に + $ a cmp $ b + `+頻度 + $ h {$ w} + と単語 + $ w + `を出力+結果をファイル 'freq’にリダイレクト

私は580,000,000語の3.3GBテキストファイルでこのコードを実行しました。 Perl 5.22は173秒で完成しました。

私の入力ファイルでは、すでに次のコードを使用して、句読点が取り除かれ、大文字が小文字に変換されています。+ + perl -pe" s / [^ a-zA-Z \ t \ n '] / / g; tr / AZ / az / "file_raw> file + +(144秒の実行時間)

'' '' '

単語カウントスクリプトは、代わりにawkで記述することもできます。+ `+ awk '{for(i = 1; i ⇐ NF; i ){h [$ i] }} END {for(w in h){ printf( "%s \ t%s \ n"、h [w]、w)}} 'ファイル| sort -rn> freq + `