C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

大量のテキストファイルをマルチスレッドで高速に読み込む方法 [2]

[トピック内 69 記事 (41 - 60 表示)]  << 0 | 1 | 2 | 3 >>

■83303 / inTopicNo.41)  Re[18]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
  
□投稿者/ とっちゃん (430回)-(2017/03/16(Thu) 18:01:21)
No83299 (金尾 さん) に返信


No83298 より
> シングルスレッドにしないと、それぞれのファイルの読み込みがぶつかり合って
> 逆に遅くなってしまいます。

えーっと。。。これは計測済みの結果ですか?試してみましたか?
SSDが対象で、一括読み込みですよね?

シングルスレッドにしないと、Cloneしてる間にデータが壊れてたり
Data_in が実行された時点で bs1 が意図したファイルの内容ではないとか
そういうことではありませんか?

まずは、
> bs をそのループの中のローカル変数としてメモリを確保。
と書いていますが、各ファイルごとにメモリを用意して、
Cloneをしないように書き換えてみるところから始めることをお勧めします。

引用返信 編集キー/
■83304 / inTopicNo.42)  Re[20]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (184回)-(2017/03/16(Thu) 18:02:36)
No83302 (なちゃ さん) に返信
> でもって、タスクは実際には10もいらないはずです。多分一つでも効果があります。
> これは、一つでもタスクを使うと、読み込みとデータ処理が並列に動作できるようになるからです。

一つでもと書きましたか、今のコードだとだめでした、毎回待機してしまうので。
というか、今のコードだと読み込み前に待機してしまうので必ず処理がストールします。

タスク群のループ毎に独立したバッファを用意して、読み込みまでは全開のタスクと並列で行い、読み込み後に全開のタスクの完了を待つというのが効率的な流れです。

引用返信 編集キー/
■83305 / inTopicNo.43)  Re[20]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (26回)-(2017/03/16(Thu) 18:06:03)
No83302 (なちゃ さん) に返信

ストレージからのデータ読み込み時間>>データの処理時間
の時にはタスクは1つでも十分なのですが、
ストレージからのデータ読み込み時間<<データの処理時間
という条件の時には、タスクは複数あった方が高速処理が可能となります。

そして大きな問題は何度も申し上げているのですが、
同時に複数ファイルにアクセスすると読み込み速度が落ちてしまうことです。
そのため、ファイル読み込み部分のみシングルスレッドで
処理部分だけをマルチスレッド化する必要があります。


Dim fs As New FileStream(StrPath(k), FileMode.Open, FileAccess.Read)
fs.Read(bs, 0, file_size)
fs.Close()

MultiTask_in(1) = New Task(AddressOf Data_in, bs)
MultiTask_in(1).Start()



Dim fs As New FileStream(StrPath(k), FileMode.Open, FileAccess.Read)
fs.Read(bs, 0, file_size)
fs.Close()

MultiTask_in(2) = New Task(AddressOf Data_in, bs)
MultiTask_in(2).Start()

・・・

のようにして、10個の処理を書き下せば高速処理できるのは分かるのですが
コードが見づらくなってしまいます。

できればfor文でまとめてしまいたいのですが
どのようにすれば良いでしょうか?

散々、考えたのですが、
クローンコピーを使わない限り、他に方法がないように思うのですが
良い方法があれば教えてください。

あと、ファイルサイズは固定です。







引用返信 編集キー/
■83306 / inTopicNo.44)  Re[21]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (27回)-(2017/03/16(Thu) 18:10:32)




とっちゃんさん


> えーっと。。。これは計測済みの結果ですか?試してみましたか?
> SSDが対象で、一括読み込みですよね?
SSDだとほとんど変わりませんが、
HDDだと速度が低下します。
SSDだけでなくHDDでも使えるように最適化したいと考えています。



> シングルスレッドにしないと、Cloneしてる間にデータが壊れてたり
> Data_in が実行された時点で bs1 が意図したファイルの内容ではないとか
> そういうことではありませんか?
この辺りは検証できておりません。



> まずは、
> > bs をそのループの中のローカル変数としてメモリを確保。
> と書いていますが、各ファイルごとにメモリを用意して、
> Cloneをしないように書き換えてみるところから始めることをお勧めします。
全て書き下す方法だと所望することが実現できているのですが
コードが冗長的になってしまうため
できればForやEachなどを使いたいと考えています
タスクの数が10個と決まっていれば良いですが
30とかに増やしたい場合に大変です。



引用返信 編集キー/
■83307 / inTopicNo.45)  Re[18]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ PANG2 (162回)-(2017/03/16(Thu) 18:36:55)
No83298 (金尾 さん) に返信
> ありがとうございます。
>
> Parallel.For() というものをしらなかったので非常に勉強になりました。
>
> ただ、今回のケースでは、
> 上で書いたように、ファイルを読み込む場所だけは
> シングルスレッドにしないと、それぞれのファイルの読み込みがぶつかり合って
> 逆に遅くなってしまいます。
>
> どうするのがもっともスマートな書き方でしょうか?

ファイルを読み込む場所をlock
引用返信 編集キー/
■83308 / inTopicNo.46)  Re[19]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (28回)-(2017/03/16(Thu) 19:02:56)
ありがとうございます。

試してみたのですが逆に10%程度ですが、更に遅くなってしまいました。



Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)

SyncLock fs

fs.Read(bs, 0, file_size)
fs.Close()


End SyncLock


という場所をロックしたのですが
場所は合っていますでしょうか?

引用返信 編集キー/
■83309 / inTopicNo.47)  Re[20]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (185回)-(2017/03/16(Thu) 19:36:44)
各ファイルの具体的な処理内容がわからないので負荷の程度は分かりませんが、まあ高い確率で状況を正確に把握できていないだけか、無駄な処理をやってしまっているかで、実際はディスクの方が遅い気がしますけどね、それはさておき。

今の流れだと、タスクが終わってから次の読み込みまでに必ず待機が入るので、最速にはなりません。
タスク数はCPUコア数以上にしても無駄ですし、実際ディスク読み込みよりも10倍かかるなんてよっぽどです。

どちらかというと、例えばバッファ10こをリソースと考えて、空く度に使い回すという流れの方がシンプルにできそうです。

引用返信 編集キー/
■83310 / inTopicNo.48)  Re[21]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (29回)-(2017/03/16(Thu) 19:41:11)
No83309 (なちゃ さん) に返信


そうですよね
これってbsのバイト配列を毎回生成、削除していますよね

やはり10個のタスクを書き下すしか方法がないのでしょうか?
 
引用返信 編集キー/
■83311 / inTopicNo.49)  Re[21]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (186回)-(2017/03/16(Thu) 19:44:52)
例えばブロッキングキューを使って、まずバッファを10こ保存します。

ファイル全部のループを開始し、ブロッキングキューからバッファ取り出して、ファイルを読み込んでタスク起動します。
各タスクは、渡されたバッファを処理して、終了時にブロッキングキューにバッファを戻します。

これだけでうまいこと効率的に動くはずです。
ボトルネックがディスク側でも処理側でも、おおむね最大効率で動けるはずです。
引用返信 編集キー/
■83312 / inTopicNo.50)  Re[22]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (30回)-(2017/03/16(Thu) 19:49:52)
> ■No83309 (なちゃ さん) に返信


ありがとうございます。

ブロッキングキューとは何でしょうか?
検索してもよく分からなかったので
コードを書いていただけないでしょうか?
 
引用返信 編集キー/
■83314 / inTopicNo.51)  Re[20]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ PANG2 (163回)-(2017/03/16(Thu) 22:42:52)
No83308 (金尾 さん) に返信
> ありがとうございます。
> 
> 試してみたのですが逆に10%程度ですが、更に遅くなってしまいました。
> 
> 
> 
>                 Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
> 
>             SyncLock fs
> 
>                 fs.Read(bs, 0, file_size)
>                 fs.Close()
> 
> 
>             End SyncLock
> 
> 
> という場所をロックしたのですが
> 場所は合っていますでしょうか?

場所も方法も間違っています。
こんな感じ

Dim LockObj As New Object
Parallel.For(略
	Dim bs() As Byte
	SyncLock LockObj //二重侵入を禁止
		Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
		fs.Read(bs, 0, file_size)
		fs.Close()
	End SyncLock

	//bsを文字列配列に変換して処理

引用返信 編集キー/
■83315 / inTopicNo.52)  Re[21]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (187回)-(2017/03/17(Fri) 08:30:27)
Parallel.Forは大変便利なツールですが、最大効率を求めて使うものではありません。
そのしくみ上まず確実に非効率が発生します。
また内部でロックするのは定石的には悪手です。
最初の方法とどちらが効率的かは判断が難しいですが、悪化する可能性がそれなりに高いように思います。
シンプルに実装できるのでそれがメリットですが。
引用返信 編集キー/
■83321 / inTopicNo.53)  Re[22]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (188回)-(2017/03/17(Fri) 11:19:19)
携帯から手打ちしかできない状態なので多分ミスはあると思いますが、例えばおおむねこんな感じ

Dim count As New CountDownEvent(filePaths.Length)

' これがいわゆるブロッキングキュー
Dim queue As New BlockingCollection(Of Byte())()
Dim maxBuffers = 10
Dim bufferCount = 0

For Each filePath In filePaths
    Dim buff As Byte() = Nothing
    If bufferCount < maxBuffers Then
        If Not queue.TryTake(buff) Then
            ' 最大数まで作成してなくて空きがないなら新規作成
            buff = New Byte(fileLength - 1){}
            bufferCount += 1
        End If
    Else
        ' 最大数まで作成済みならバッファ空くまで待機
        buff = queue.Take()
    End If

    ' buffにファイル読み込み

    Task.Factory.StartNew(
        Sub()
            ' buffの処理

            ' 処理の終わりを通知してバッファをキューに戻す
            count.Signal()
            queue.Add(buff)
        End Sub
End For

' タスクが全部完了するまで待機
count.Wait()

引用返信 編集キー/
■83322 / inTopicNo.54)  Re[22]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (31回)-(2017/03/17(Fri) 11:30:56)

PANG2さん


ありがとうございます。


Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
SyncLock fs
fs.Read(bs, 0, file_size)
fs.Close()
End SyncLock



Dim LockObj As New Object
Parallel.For(略
Dim bs() As Byte
SyncLock LockObj //二重侵入を禁止
Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
fs.Read(bs, 0, file_size)
fs.Close()
End SyncLock


の2つの方法で速度を比較したのですが
SSD、HDDともに、私が最初に提示した一つ目の方法の方が10〜20%程度高速な結果が得られました。


ただ、気になるのですが、
上でも書いた通りですが
HDDの場合には、Taskを使った方法よりもParallel.Forの方が10〜20%程度遅いです。

一方で、SSDの場合には、なぜかこれが逆転してParallel.Forの方が
10〜20%速くなります。

Taskだと410MB/s程度しか読み込み速度が上がらないのに
Parallel.Forだと510MB/sまで速度が上がります。

計算処理の有無が関係しているのではないかと思い、
読み込みだけのコードを書いてみましたが、やはり変わりませんでした。

実際に総読み込み時間を測定してみても
やはりParallel.Forの方が速いです。

もちろん、メモリは毎回クリアして測定しています。

不思議な結果となりました。




なちゃ さん
ブロッキングキューというものの使い方を教えていただけないでしょうか?

引用返信 編集キー/
■83324 / inTopicNo.55)  Re[23]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (32回)-(2017/03/17(Fri) 11:36:51)
なちゃ さん
ありがとうございます。

後ほど試してみます。

上で、TaskとParallel.Forを比較したと書きましたが
Taskというのはマルチスレッド関係無しで
Forループで単にファイルをバイト配列を読み込むのを繰り返しているだけです。

Parallel.Forの方は排他処理を行っているので、
同じ速度になるはずなのに、不思議です・・・

引用返信 編集キー/
■83325 / inTopicNo.56)  Re[24]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (33回)-(2017/03/17(Fri) 11:40:01)
510MB/sではなく560MB/sでした
引用返信 編集キー/
■83327 / inTopicNo.57)  Re[25]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (35回)-(2017/03/17(Fri) 12:01:57)



方法1

Dim bs00(CInt(file_size - 1)) As Byte

For i As Integer = 1 To filenum
Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
fs.Read(bs00, 0, file_size)
fs.Close()
Next i

方法2

Parallel.For(1, filenum, Sub(i)

Dim bs0(CInt(file_size - 1)) As Byte

Dim fs As New FileStream(StrPath(i), FileMode.Open, FileAccess.Read)
SyncLock fs
fs.Read(bs0, 0, file_size)
End SyncLock
fs.Close()

End Sub)


方法1だと、SSDで410MB/s、HDDで140MB/s
方法2だと、SSDで550MB/s、HDDで110MB/s

という転送速度になるのですが、
理由は分かりますか?

やっていることは同じなのに
なぜかSSDとHDDで方法によってここまで速度差が見られるのはなぜなのでしょうか?





引用返信 編集キー/
■83328 / inTopicNo.58)  Re[26]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ 金尾 (36回)-(2017/03/17(Fri) 12:03:55)
もしかしてデータが読み込まれていないのではないかと思い
読み込んだデータを出力してもみましたが
両方とも正常に読み込むことができています。
 
引用返信 編集キー/
■83329 / inTopicNo.59)  Re[25]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
□投稿者/ なちゃ (189回)-(2017/03/17(Fri) 12:06:09)
ちょっとミスを見つけたため修正。

Forの終わりをNextに直したのと、End Sub の後に)つけたの、あと一応CountdownEventの大文字小文字修正。

あと、ファイルオープン位置はもっと前にする方が良いかもしれないのでコメント追加。

Dim count As New CountdownEvent(filePaths.Length)

' これがいわゆるブロッキングキュー
Dim queue As New BlockingCollection(Of Byte())()
Dim maxBuffers = 10
Dim bufferCount = 0

For Each filePath In filePaths
    ' ファイルのオープンはここの方がいいかも(オープンのみ)

    Dim buff As Byte() = Nothing
    If bufferCount < maxBuffers Then
        If Not queue.TryTake(buff) Then
            ' 最大数まで作成してなくて空きがないなら新規作成
            buff = New Byte(fileLength - 1){}
            bufferCount += 1
        End If
    Else
        ' 最大数まで作成済みならバッファ空くまで待機
        buff = queue.Take()
    End If

    ' buffにファイル読み込み

    Task.Factory.StartNew(
        Sub()
            ' buffの処理

            ' 処理の終わりを通知してバッファをキューに戻す
            count.Signal()
            queue.Add(buff)
        End Sub)
Next

' タスクが全部完了するまで待機
count.Wait()

引用返信 編集キー/
■83330 / inTopicNo.60)  Re[27]: 大量のテキストファイルをマルチスレッドで高速に読み込む方法
 
□投稿者/ なちゃ (190回)-(2017/03/17(Fri) 12:11:10)
No83328 (金尾 さん) に返信
> もしかしてデータが読み込まれていないのではないかと思い
> 読み込んだデータを出力してもみましたが
> 両方とも正常に読み込むことができています。

単にシーケンシャルに処理を書いた場合、ファイル読み込みが終わってから次のリードでファイル転送を開始するまでに空きが入るため、SSDだとマックスの効率にならないとか(この理由だとブロッキングキュー使ったものでも同じ問題あるのでさらに工夫が必要)。
HDDの場合、逆に同時オープンすることで物理シークが発生して効率悪化という可能性がありますが、同時オープンしてなくても変わらないという話もある感じなので何ともいえません。
ディスク処理の最適化ってなかなか難しいです。

引用返信 編集キー/

このトピックをツリーで一括表示

<前の20件 | 次の20件>
トピック内ページ移動 / << 0 | 1 | 2 | 3 >>

管理者用

- Child Tree -