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

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

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

巨大なバイナリファイルを高速で出力する方法

[トピック内 19 記事 (1 - 19 表示)]  << 0 >>

■83674 / inTopicNo.1)  巨大なバイナリファイルを高速で出力する方法
  
□投稿者/ バカボドン (15回)-(2017/04/01(Sat) 21:32:39)

分類:[.NET 全般] 

http://bbs.wankuma.com/index.cgi?mode=al2&namber=83387
この質問ページで巨大なテキストファイルを高速で出力する方法を教えていただきました。

次は、バイト配列から巨大なバイナリファイルを高速で出力したいと考えています。

元となるコードは以下の物です。

Dim a(xmax, ymax, zmax) As Byte


        Using stream As Stream = New FileStream(Path, FileMode.Create)
            Using writer As New BinaryWriter(stream)

                For z As Integer = 1 To zmax
                    For y As Integer = 1 To ymax
                        For x As Integer = 1 To xmax

                            writer.Write(a(x, y, z))

                        Next x
                    Next y
                Next z

            End Using
        End Using


これをどのようにすれば
マルチスレッドを使って高速化することができますか?

既にバイト化されているので、
マルチスレッドを使ったとしても高速化するのは無茶でしょうか?




引用返信 編集キー/
■83675 / inTopicNo.2)  Re[1]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (16回)-(2017/04/01(Sat) 22:54:51)
2017/04/01(Sat) 22:55:00 編集(投稿者)
            Dim sabun As Integer = xmax * ymax
            Parallel.For(1, zmax + 1,
                    Sub(z)

                        Dim h As Integer = sabun * (z - 1)

                        For y As Integer = 1 To ymax

                            For x As Integer = 1 To xmax

                                buffer(h) = (a(x, y, z))
                                h += 1

                            Next x
                        Next y

                    End Sub)



            Using fs As New System.IO.FileStream(TempPath, System.IO.FileMode.Create, System.IO.FileAccess.Write)
                                     fs.Write(buffer, 0, buffer.Length)
                                 End Using


これで、数倍速くなりました

これが限界でしょうか?

引用返信 編集キー/
■83676 / inTopicNo.3)  Re[2]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ もりお (35回)-(2017/04/01(Sat) 23:31:34)
2017/04/01(Sat) 23:31:43 編集(投稿者)
No83675 (バカボドン さん) に返信

シングルスレッドの方が速いのじゃないかなと思いました。
これどうでしょう?

Dim Data(a.Length - 1) As Byte
Buffer.BlockCopy(a, 0, Data, 0, a.Length)

Dim BufferSize = 1024 * 64

Using Stream As New System.IO.FileStream(TempPath, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Read, BufferSize, False)
    Stream.Write(Data, 0, Data.Length)
End Using

引用返信 編集キー/
■83677 / inTopicNo.4)  Re[3]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (17回)-(2017/04/01(Sat) 23:44:34)


ありがとうございます。

もとの3次元配列には
1〜maxの中にデータ入っています
Buffer.BlockCopy(a, 0, Data, 0, a.Length)
の前に
0〜max-1にコピーして移す必要があると思います。
そのときにマルチスレッドは有効ではないでしょうか?

あと、


Using fs As New System.IO.FileStream(TempPath, System.IO.FileMode.Create, System.IO.FileAccess.Write)
fs.Write(buffer, 0, buffer.Length)
End Using


とするのと

Dim BufferSize = 1024 * 64

Using Stream As New System.IO.FileStream(TempPath, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Read, BufferSize, False)
Stream.Write(Data, 0, Data.Length)
End Using

のようにバッファサイズを指定して書き込むのでは
何が違うのでしょうか?



引用返信 編集キー/
■83678 / inTopicNo.5)  Re[4]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ もりお (36回)-(2017/04/02(Sun) 00:23:48)
No83677 (バカボドン さん) に返信

データがつながってないのでBuffer.BlockCopyではダメなのですね。
データのコピーをマルチスレッドで行うのは有効だと思います。
それを否定できる情報を私が持っていないという弱々しい思いで恐縮ですが
有効な可能性はありますね。

FileStreamのバッファサイズについてですが
FileStream内部で使用するバッファのサイズを64kにしてみました。
無指定のときは4kです。ディスクに書き込む回数が減るので多少は速くなるのじゃないかなと思いました。
あまり大きくすると逆効果らしいですが、64kくらいまでは速くなるのじゃないかなと、そういうねらいです。

引用返信 編集キー/
■83679 / inTopicNo.6)  Re[5]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (18回)-(2017/04/02(Sun) 09:50:44)
No83678 (もりお さん) に返信
ありがとうございます。

調べてみると
https://channel9.msdn.com/Forums/TechOff/Dramatic-FileStreamWrite-performance-difference-based-on-passed-in-buffer-size-Why

このページに、64k〜128Mがぐらいが良いと書かれてありました
CPUのキャッシュサイズに合わせるのが良いそうです

実際にいろいろとサイズを変えて試してみたのですが
私の環境ではなぜか1〜3%程度しかバッファサイズ依存性がありませんでした。
デフォルトよりかは64kの方が速いですが、2%前後しかありません。
なぜでしょうか・・・?
 
引用返信 編集キー/
■83680 / inTopicNo.7)  Re[6]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ shu (997回)-(2017/04/02(Sun) 10:31:48)
No83679 (バカボドン さん) に返信

配列a を多次元でなく1次元配列で扱うようにするのはどうでしょう?

その配列をbとすると

a(x,y,z) = b(z*((xmax+1) * (ymax+1)) + y * (xmax+1) + x)

となるような実装にしておけばbを単純に書き込むだけで済むので速くなりそうな気がします。

bへの読み書きもインデックスが簡単に求められるので速度低下は発生しないものと考えられます。
引用返信 編集キー/
■83681 / inTopicNo.8)  Re[6]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ もりお (37回)-(2017/04/02(Sun) 12:01:33)
No83679 (バカボドン さん) に返信

> デフォルトよりかは64kの方が速いですが、2%前後しかありません。
> なぜでしょうか・・・?

そういうものなのだと思います。バッファのサイズは誤差みたいなものでしたね。
引用返信 編集キー/
■83683 / inTopicNo.9)  Re[7]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ なちゃ (206回)-(2017/04/02(Sun) 15:40:17)
No83681 (もりお さん) に返信
> ■No83679 (バカボドン さん) に返信
>
>>デフォルトよりかは64kの方が速いですが、2%前後しかありません。
>>なぜでしょうか・・・?
>
> そういうものなのだと思います。バッファのサイズは誤差みたいなものでしたね。

前にも書きましたが、大きな影響があるのは延書き込みが効くかどうかです。遅延書き込みが効いているうちはほぼCPU処理です。
バッファーサイズを増やすとカーネルモードコールの回数が減ることで若干の効果がありますが、劇的には変わりません。
ディスクへの書き込みはシステムのメモリマネージャが専用スレッドで適宜実行する(これが遅延書き込み)ので、デイ好く書き込み回数は、バッファーサイズとはほとんど関係ありません。

で以前は256KBくらいを越えると遅延書き込みが効かなくなるため一気にパフォーマンスが落ちていましたが、リンク先を見ると数十MB程度でそれが起きているようですね。
OSバージョンやメモリ量などでいろいろとチューニングが変わってきているのでしょう。
引用返信 編集キー/
■83685 / inTopicNo.10)  Re[8]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ なちゃ (207回)-(2017/04/02(Sun) 16:43:55)
ひとつ忘れてたので補足です。
FileStreamのバッファは、バッファサイズに満たない書き込みを繰り返す場合には意味を持つ(使われる)のですが、一度にバッファサイズを越える書き込みを行う場合にはバイパスされたと思います。

なので、最初のように1バイトずつ書くコードの場合はともかく、まとめて書き込むようにすると、まとめたサイズよりバッファサイズが小さいうちは何変化はおこらないことになります。
※そもそもバッファ処理を自分でやってるのと同じなので、あえて指定する意味はないでしょう。
引用返信 編集キー/
■83686 / inTopicNo.11)  Re[9]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ もりお (38回)-(2017/04/02(Sun) 17:30:56)
No83685 (なちゃ さん) に返信

> FileStreamのバッファは、バッファサイズに満たない書き込みを繰り返す場合には意味を持つ(使われる)のですが、一度にバッファサイズを越える書き込みを行う場合にはバイパスされたと思います。

なるほど、まさにナンセンスでしたね。
引用返信 編集キー/
■83687 / inTopicNo.12)  Re[9]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (20回)-(2017/04/02(Sun) 17:32:08)
いろいろと情報ありがとうございます。

1バイトずつ書き込む方法でも
バッファサイズ依存性を調べてみましたが、
デフォルトの4kと比べて64kで20%程度だけ高速化が見られました

もっと小さくしたり大きくしましたが
64kあたりがもっとも高速化できました。

解決済み
引用返信 編集キー/
■83688 / inTopicNo.13)  Re[10]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (21回)-(2017/04/02(Sun) 17:36:15)
すいません、ちなみに質問なのですが、
マルチスレッドを使って2つのスレッドから同時にファイルの書き込みを行ったとして
バッファがなくなるまでは、別のスレッドの書き込みは行われないのでしょうか?

それともバッファがなくならない書き込み途中であっても
別のスレッドからストレージへのアクセス命令が来たら
いったん、そちらに移るのでしょうか?
 
引用返信 編集キー/
■83689 / inTopicNo.14)  Re[11]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ もりお (39回)-(2017/04/02(Sun) 19:10:43)
No83688 (バカボドン さん) に返信

FileStreamはスレッドセーフではありませんので
同期化するコードを書かなければ、FileStreamの処理で別のスレッドがブロックされることはありません。
同期化せずに複数のスレッドでFileStreamを共有して使用すると動作は保証されないです。

引用返信 編集キー/
■83690 / inTopicNo.15)  Re[12]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (23回)-(2017/04/02(Sun) 19:17:20)
ありがとうございます。

FileStreamを共有して使うという意味ではありません
スレッドごとに別で生成して使った場合はどうですか?
 
引用返信 編集キー/
■83693 / inTopicNo.16)  Re[13]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ なちゃ (208回)-(2017/04/03(Mon) 10:26:36)
FileStreamは、同時に同じファイルに出力されることについて何も制御を行いません。
ですので、複数のスレッドから同時に書き込みを行うと、当然書き込みが競合してファイル内容が混ざるなどの不都合が発生します。
引用返信 編集キー/
■83694 / inTopicNo.17)  Re[14]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (24回)-(2017/04/03(Mon) 10:42:05)
No83693 (なちゃ さん) に返信
違うファイルに対して書き込みを行った場合の話です
 
引用返信 編集キー/
■83697 / inTopicNo.18)  Re[15]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ Azulean (811回)-(2017/04/03(Mon) 12:36:08)
No83694 (バカボドン さん) に返信
> 違うファイルに対して書き込みを行った場合の話です

すでに書かれていることからご理解いただいていると思いますが、OS がメモリに溜めておく(遅延書き込み)部分を越えるようなことになれぱ、速くならないどころか、HDD の場合はシークの無駄が増えて遅くなりそうです。
遅延書き込み回りの OS のバージョンに依存する部分やハードウェア環境に左右されるところまで狙うのはおすすめできませんね。
引用返信 編集キー/
■83698 / inTopicNo.19)  Re[16]: 巨大なバイナリファイルを高速で出力する方法
□投稿者/ バカボドン (25回)-(2017/04/03(Mon) 17:05:12)
いろいろと勉強になりました
解決済み
引用返信 編集キー/

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


トピック内ページ移動 / << 0 >>

このトピックに書きこむ