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

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

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

巨大なテキストファイルを高速で出力する方法

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

■83387 / inTopicNo.1)  巨大なテキストファイルを高速で出力する方法
  
□投稿者/ バカボドン (1回)-(2017/03/20(Mon) 16:51:51)

分類:[.NET 全般] 

2017/03/20(Mon) 16:53:45 編集(投稿者)
2017/03/20(Mon) 16:53:06 編集(投稿者)
<pre><pre>2017/03/20(Mon) 16:52:46 編集(投稿者)
2017/03/20(Mon) 16:52:43 編集(投稿者)

<pre><pre>
以下のコードで巨大なSingle配列を
数百MByte程度のサイズの一列のテキストファイルとして出力するコードを書きました。

Dim a(xmax, ymax, zmax) As Single

Using writer As New StreamWriter(Path, False, Encoding.ASCII)

       For zzz As Integer = 1 To zmax
       For yyy As Integer = 1 To ymax
       For xxx As Integer = 1 To xmax

            writer.WriteLine(a(xxx, yyy, zzz).ToString)

       Next xxx
       Next yyy
       Next zzz

End Using

ただ、タスクマネージャーで確認すると
ストレージの帯域は5%程度しか使っておらず
CPU使用率も10%程度となっています。

これをマルチスレッド化して高速出力したいのですが
どのようにすれば良いですか?

Single→String→Byteという2段階のデータ変換を行っていることになるので
どちらが律速しているか調べるために
以下のマルチスレッドコードを書いて計算時間を比較しました。



       For zzz As Integer = 1 To zmax

           Dim XY(xmax, ymax) As String

           Dim zzz2 As Integer = zzz

          Parallel.For(1, ymax + 1, Sub(yyy)

            For xxx As Integer = 1 To xmax

                XY(xxx, yyy) = a(xxx, yyy, zzz2).ToString

            Next xxx

          End Sub)

       For yyy As Integer = 1 To ymax
       For xxx As Integer = 1 To xmax

            writer.WriteLine(XY(xxx, yyy))

       Next xxx
       Next yyy
       Next zzz

しかし計算時間は全く変わりませんでした。
そのため、String→Byteの変換が律速していると考えています。


Dim bytesData As Byte() = System.Text.Encoding.GetEncoding(932).GetBytes(str)

というコードで、String→Byte配列への変換を行うことができるのは分かるのですが、
どのようにマルチスレッド化すれば良いでしょうか?

ファイルサイズが数百MByte程度になるので
一度に全てをバイト配列にして最後に出力するというのは避けたいです。


       For zzz As Integer = 1 To zmax
の部分を4つくらいのスレッドの分離して
マルチスレッドで処理するのがもっとも適当でしょうか?



</pre></pre></pre></pre>

引用返信 編集キー/
■83390 / inTopicNo.2)  Re[1]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ Azulean (801回)-(2017/03/20(Mon) 20:44:40)
2017/03/20(Mon) 21:59:51 編集(投稿者)

No83387 (バカボドン さん) に返信
> ただ、タスクマネージャーで確認すると
> ストレージの帯域は5%程度しか使っておらず
> CPU使用率も10%程度となっています。

そういう測り方をするのではなく、Visual Studio のパフォーマンス分析機能などのプロファイリングツールを使い、シングルスレッドで解決不可能と判断できる根拠を得るところからでしょう。
最近のスレッドにも書かれていますが、並列化すれば速くなるとは限りませんので、分析が先です。
そして、タスクマネージャでは自分のコードのどこが原因かわからないので、分析のためのツールとしては不適当です。

> これをマルチスレッド化して高速出力したいのですが
> どのようにすれば良いですか?

今のコードからすると無理かと思います。
文字列化で長さが不定ですし、分散して1つの連続したファイルにするのは無茶です。
(Windows プログラムとしては、ファイルの任意の位置にバイト列を差し込むことも、ファイルの中にある無駄な隙間を詰めることもできないので、長さ不定のブロックを複数スレッドで同一ファイルに書き込んで、連続した1ファイルにすることは実現できない)


ところで、派生ネタで質問される際は同じ名前にしていただいた方がよいです。
前に話した内容をお互いに前提にできるので楽です。
(さすがに、こんな短期間でこのような特徴的なものが別人である可能性は低そうなので...)
引用返信 編集キー/
■83391 / inTopicNo.3)  Re[2]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ なちゃ (193回)-(2017/03/20(Mon) 23:01:15)
ぞもそも数百NB程度だと多分ほぼ遅延書き込みになってしまうので、ディスク帯域はあまり参考になりません。
これはディスク処理をCPU処理で肩代わりしているともいえる状況ですので、マルチスレッド化できる部分で早くできるとは限りません。
実際このシンプルなコードであまり工夫する価値はないと思います。
最終的にディスク書き込みするバイト情報の作成と、書込みの実行を並列に行えば多少は高速化できるかもしれませんが。
引用返信 編集キー/
■83393 / inTopicNo.4)  Re[3]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ なちゃ (194回)-(2017/03/20(Mon) 23:20:52)
あと、そうやって工夫したところで、遅延書き込みの状況次第で、わずかでもストールすると全体にかかる時間は全然変わってきますので、工夫で変わるレベルなんてほぼ無意味になります。
引用返信 編集キー/
■83394 / inTopicNo.5)  Re[4]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (2回)-(2017/03/20(Mon) 23:31:31)
No83393 (なちゃ さん) に返信
> 最終的にディスク書き込みするバイト情報の作成と、書込みの実行を並列に行えば多少は高速化できるかもしれませんが。

できれば、これを試してみたいと考えています。
もしかするとあまり速くならないかも知れませんが
どう改良すべきかお教え願えないでしょうか?
 
引用返信 編集キー/
■83395 / inTopicNo.6)  Re[1]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ shu (983回)-(2017/03/20(Mon) 23:32:57)
No83387 (バカボドン さん) に返信

> ファイルサイズが数百MByte程度になるので
> 一度に全てをバイト配列にして最後に出力するというのは避けたいです。
この考え方が一番速く出来る可能性が高いと思います。
一度に出来ないならいくつかにわければよいのではないでしょうか?
例えば10MB単位で書くとか。

10MB×2のバイト配列を用意して
(1) 1つめのバイト配列に書き込む
(2) 1つめのバイト配列をファイルに書き込む
(3) 2つめのバイト配列に書き込む((2)とスレッドを分けることが可能)
(4) 2つめのバイト配列をファイルに書き込む((2)を処理したスレッドが(3)での処理が終了していることを確認して書き込む)
同様に繰り返す

とすれば2つのスレッドで多少は速度を稼げそうな気もします。同期をうまくとる必要があります。
用意出来る配列の本数が多ければ配列への書き込み自体は並列度が上がりますが書き込み待ち制御が大変にはなるでしょう。

また今回の処理がSingle値の文字列化であり配列に収まるデータ数が可変になってしまうので配列の大きさを固定化しないで
データ数で分けたほうがやりやすいかもしれません。

引用返信 編集キー/
■83401 / inTopicNo.7)  Re[5]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ なちゃ (195回)-(2017/03/21(Tue) 12:29:22)
ちなみに、10MB単位とかで書き込みを行うと、激烈に遅くなります。
※遅延書き込みが無効になるため

経験的には256KB程度までにする方がよい。
引用返信 編集キー/
■83402 / inTopicNo.8)  Re[6]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (3回)-(2017/03/21(Tue) 13:19:14)
ご回答ありがとうございます。
できれば、具体的なコードを示していただけないでしょうか?
よろしくお願いいたします。

引用返信 編集キー/
■83403 / inTopicNo.9)  Re[7]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ もりお (30回)-(2017/03/21(Tue) 20:47:23)
面白そうですね、私もちょっと書いてみようかしら。

・GetBytesとGetBytesの非同期化
・GetBytesとWriteLineの非同期化

2つの非同期化があり、それぞれでどれだけ速くなるでしょうか、
2つを組み合わせたときにどれだけ速くなるでしょうか、
というのがわかればいい感じですかね。

まずは比較の軸になるシングルスレッドのプログラムを
こしらえないといけないですね。

JITコンパイルは切った方が良さそうな気がしますが
アノテーションでなんかあれできたような。
引用返信 編集キー/
■83404 / inTopicNo.10)  Re[8]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (5回)-(2017/03/21(Tue) 21:01:30)
No83403 (もりお さん) に返信
> 面白そうですね、私もちょっと書いてみようかしら。
>

ぜひともよろしくお願いいたします。
プロの方がどういう書き方をするのか
手本にさせていただきたいと考えております。
 
引用返信 編集キー/
■83405 / inTopicNo.11)  Re[9]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ もりお (31回)-(2017/03/21(Tue) 21:13:39)
No83404 (バカボドン さん) に返信

やめて、ハードル上げるのやめて。
プロはまずVisual Studio 2017 コミュニティエディションをインストールします。
荒らしと区別つかないので実況はこれでやめようと思いますが、ちょっと書いてみます。

引用返信 編集キー/
■83406 / inTopicNo.12)  Re[10]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (6回)-(2017/03/21(Tue) 21:47:48)
No83405 (もりお さん) に返信
素晴らしいプログラムコードをお待ちしておりますので
どうぞよろしくお願いいたします。

引用返信 編集キー/
■83408 / inTopicNo.13)  Re[11]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ もりお (32回)-(2017/03/22(Wed) 01:29:21)
2017/03/22(Wed) 01:37:36 編集(投稿者)

No83406 (バカボドン さん) に返信

書きました
https://paiza.io/projects/1OxpG-adPgJKBpkwDhgBzg

Parallel.Forを使えば単純に速くなるというものでもありませんでした。
外側のループを増やすと非同期の方が遅くなります。
Parallel.Forを呼び出す回数を減らさないと裏目にでますねこれ。

書き込みの方も同様で外側のループを減らすと非同期の方が速くなりました。




引用返信 編集キー/
■83410 / inTopicNo.14)  Re[12]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (7回)-(2017/03/22(Wed) 09:22:08)
素晴らしい計算コードをありがとうございます。

仰る通り、
300,300,300の条件で50%程度
3,3000,3000の条件で90%程度しか速くなりませんでした。
3000,3000,3の条件では逆に6分の1程度に速度が低下してしまいました。

CPU使用率を見ると、マルチスレッド動作がうまくいっているので
数倍程度違いが見られるのに、その違いは全く計算時間に反映されていないことが分かりました。

SSDとHDDも比較しましたが、全く差異は見られませんでした。

もう少しいろいろと試してからコメントさせていただきます。

引用返信 編集キー/
■83412 / inTopicNo.15)  Re[13]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (9回)-(2017/03/22(Wed) 09:30:59)
ところで生成されたtest.txtファイルの中身が空になっているのですが
これは一体なぜでしょうか?
 
引用返信 編集キー/
■83413 / inTopicNo.16)  Re[14]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (11回)-(2017/03/22(Wed) 09:41:40)
あと、WriteLineとの方法とも比較してみましたが、
外側のループのサイズに依存せず、WriteLineの方が常に20%程度遅くなる結果が得られました
やはり、少しずつ細切れに出力するように
一気に出力する方が良いのでしょうか・・・?
引用返信 編集キー/
■83414 / inTopicNo.17)  Re[15]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (12回)-(2017/03/22(Wed) 10:18:30)
どうも、元のコードだと
GetBytesAndWriteSync
がファイルを書き込むコードになっていないようでした。

あと、
外側のループを減らすために、以下のようにxyまるごと配列にして出力する方法も試してみましたが
なぜか10倍くらい遅くなってしまいました




    <MethodImpl(MethodImplOptions.NoOptimization)>
    Sub GetBytesAndWriteASync4(ByVal a(,,) As Single, ByVal enc As Encoding, ByVal path As String)
        Using stream As New FileStream(path, FileMode.Create)


            For z As Integer = 0 To a.GetUpperBound(0)


                    Dim buf(a.GetUpperBound(1))() As Byte


                    Dim zz As Integer = z

                    Parallel.For(0, buf.Length,
                                 Sub(y)
                                buf(y) = {}
                                For x As Integer = 0 To a.GetUpperBound(2)

                                    buf(y) = buf(y).Concat(enc.GetBytes(a(zz, y, x).ToString())).ToArray()

                                Next

                                 End Sub)

                   For Each b In buf
                        stream.Write(b, 0, b.Length)
                        stream.WriteByte(&HD)       'CR
                        stream.WriteByte(&HA)       'LF
                  Next b


            Next z


        End Using
    End Sub


引用返信 編集キー/
■83418 / inTopicNo.18)  Re[16]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ PANG2 (165回)-(2017/03/22(Wed) 13:53:27)
2017/03/22(Wed) 14:18:07 編集(投稿者)
No83414 (バカボドン さん) に返信
> 外側のループを減らすために、以下のようにxyまるごと配列にして出力する方法も試してみましたが
> なぜか10倍くらい遅くなってしまいました

改良案
    Sub GetBytesAndWriteASync5(ByVal a(,,) As Single, ByVal enc As Encoding, ByVal path As String)
        Using stream As New StreamWriter(path, FileMode.Create)
            For z As Integer = 0 To a.GetUpperBound(0)
                Dim buf(a.GetUpperBound(1)) As StringWriter
                Dim zz As Integer = z
                Parallel.For(0, buf.Length,
                             Sub(y)
                                 buf(y) = New StringWriter()
                                 For x As Integer = 0 To a.GetUpperBound(2)
                                     buf(y).WriteLine(a(zz, y, x))
                                 Next
                             End Sub
                )

                For Each b In buf
                    stream.Write(b.ToString())
                Next b
            Next z
        End Using
    End Sub

追記
For Each b In buf
をTask化すれば、さらに速くなる

引用返信 編集キー/
■83420 / inTopicNo.19)  Re[17]: 巨大なテキストファイルを高速で出力する方法
□投稿者/ バカボドン (13回)-(2017/03/22(Wed) 14:28:54)

改良案ありがとうございます。

配列サイズが大きい時に高速で処理できることが分かりました


ところで、いろいろと試していて気がついたことなのですが


Dim zmax As Integer = 1
Dim ymax As Integer = 30
Dim xmax As Integer = 30

の時、
GetBytesAndWriteSync 0.0013647秒
GetBytesAndWriteASync5 0.0476627秒
と40倍くらいの差が出ます。

一方で、

Dim zmax As Integer = 300
Dim ymax As Integer = 30
Dim xmax As Integer = 30

の時、
GetBytesAndWriteSync 0.1473秒
GetBytesAndWriteASync5 0.1095秒

のように差異が狭まり1.5倍程度しか差が出なくなります。

xmaxやymaxを大きくすると
並列化処理が有効になるので、
GetBytesAndWriteASync5が高速になるのは予想できることですが
xmaxやymaxを固定したまま
zmaxを大きくしても
GetBytesAndWriteASync5が高速になるのはなぜなのでしょうか?


引用返信 編集キー/
■83421 / inTopicNo.20)  Re[18]: 巨大なテキストファイルを高速で出力する方法
 
□投稿者/ なちゃ (196回)-(2017/03/22(Wed) 15:29:53)
> xmaxやymaxを大きくすると
> 並列化処理が有効になるので、
> GetBytesAndWriteASync5が高速になるのは予想できることですが
> xmaxやymaxを固定したまま
> zmaxを大きくしても
> GetBytesAndWriteASync5が高速になるのはなぜなのでしょうか?

高速になったというよりは、オーバーヘッドを相殺できるようになったということじゃないかと思います。
処理時間が短すぎて、ライブラリのロードとかJITコンパイルによるオーバーヘッドが大きな割合を占めてしまっているというような意味です。
引用返信 編集キー/

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

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

管理者用

- Child Tree -