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

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

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

ASCIIファイルから数値データを高速読み出し

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

■100187 / inTopicNo.1)  ASCIIファイルから数値データを高速読み出し
  
□投稿者/ ロット (1回)-(2022/07/10(Sun) 19:32:04)

分類:[.NET 全般] 

 123.456 
 555.445 
 1.22234 
 6633.13 
・・・

のような各行に数値データが格納された大きなファイルサイズのテキストファイルがあり
これをVB.NETを使って、Single配列に読み出したいと考えています。

各行の文字数は固定ですが、
数値の前後に任意の数のスペースがあり、
また、小数点の位置も固定ではありません。

普通に、UTF8モードで、テキストして読み込み、
Val()で数値化すると数秒程度かかるため、
これをできるだけ短縮したいと考えています。

恐らく、バイトデータをテキストデータに変換するところが
もっとも時間がかかっているはずなので、
バイトデータから直接数値データに変換できれば
高速化できるのではないかと考えています。


普通にやるなら、
If文を使って、

If ByteValue = 48 then

	Out = 0 

Elseif ByteValue = 49 then

	Out = 1

Elseif ByteValue = 50 then

	Out = 2

・・・

みたいなIf文を使って、各文字列(バイトデータ)を数値化してから、

OutWhole = Out1 * 1000 + Out2 * 100 + Out3 * 10 +・・・

みたいに各行ごとに数値の合計値を出力すれば良いとは思います。

でも、普通にテキストファイルから変換するのと比べて
こちらの方が何倍くらい高速化が見込めるのでしょうか?

2ビットの場合、ビットシフトが高速だと思いますが
10進数の場合には上記のように足し算するしかないのでしょうか?

また、上記の方法よりも
もっと高速化できるアルゴリズムがあれば
お教えいただけないでしょうか?

引用返信 編集キー/
■100188 / inTopicNo.2)  Re[1]: ASCIIファイルから数値データを高速読み出し
□投稿者/ WebSurfer (2527回)-(2022/07/10(Sun) 20:52:49)
No100187 (ロット さん) に返信

どこがボトルネックになっているかできる限り範囲を絞って調べることは
できませんか?
引用返信 編集キー/
■100189 / inTopicNo.3)  Re[2]: ASCIIファイルから数値データを高速読み出し
□投稿者/ ロット (2回)-(2022/07/10(Sun) 21:03:30)
No100188 (WebSurfer さん) に返信
> ■No100187 (ロット さん) に返信
>
> どこがボトルネックになっているかできる限り範囲を絞って調べることは
> できませんか?

ボトルネックになっているのは
バイトデータをテキストデータに変換するところ
で間違いありません。

引用返信 編集キー/
■100190 / inTopicNo.4)  Re[1]: ASCIIファイルから数値データを高速読み出し
□投稿者/ KOZ (272回)-(2022/07/10(Sun) 21:09:33)
No100187 (ロット さん) に返信
> のような各行に数値データが格納された大きなファイルサイズのテキストファイルがあり

大きなファイルってどのくらいの大きさなんでしょうか?

> 普通に、UTF8モードで、テキストして読み込み、
> Val()で数値化すると数秒程度かかるため、

どのようなコードを書いていますか?

引用返信 編集キー/
■100191 / inTopicNo.5)  Re[2]: ASCIIファイルから数値データを高速読み出し
□投稿者/ ロット (3回)-(2022/07/10(Sun) 21:20:22)

> 大きなファイルってどのくらいの大きさなんでしょうか?
>

数MByte〜数百MByteくらいです。


> どのようなコードを書いていますか?
>
普通にStreamReaderのReadLineで一秒ずつ読み込んで
Val関数で数値化しています。
引用返信 編集キー/
■100192 / inTopicNo.6)  Re[3]: ASCIIファイルから数値データを高速読み出し
□投稿者/ KOZ (273回)-(2022/07/10(Sun) 21:38:15)
No100191 (ロット さん) に返信
>>大きなファイルってどのくらいの大きさなんでしょうか?
> 数MByte〜数百MByteくらいです。

さすがに 32 ビットの Windows だと厳しいですが、メモリを潤沢に積んだ
64 ビット Windows なら一挙に読み込んで処理することができそうです。

>>どのようなコードを書いていますか?
> 普通にStreamReaderのReadLineで一秒ずつ読み込んで
> Val関数で数値化しています。

たしかに逐次変換処理が走るので遅そうですね。
ただ、VB.NET で変換なしで処理するとかえって遅くなりそうな気もします。
さて、どうしたものか・・・

引用返信 編集キー/
■100193 / inTopicNo.7)  Re[4]: ASCIIファイルから数値データを高速読み出し
□投稿者/ KOZ (274回)-(2022/07/10(Sun) 22:07:05)
2022/07/10(Sun) 22:41:58 編集(投稿者)
No100192 (KOZ) に返信
> ただ、VB.NET で変換なしで処理するとかえって遅くなりそうな気もします。

Public Function BytesToSingle(value As Byte()) As Single
    Dim summary As Long
    Dim decimalPoint As Integer = value.Length - 1
    For i As Integer = 0 To value.Length - 1
        If value(i) = &H2E Then
            decimalPoint = i
        Else
            summary = summary * 10 + value(i) - &H30
        End If
    Next
    Return CSng(summary / (10 ^ (value.Length - decimalPoint - 1)))
End Function

とりあえず、こんな形でメソッドを作ってみましたが、どうなんでしょうね?

引用返信 編集キー/
■100194 / inTopicNo.8)  Re[5]: ASCIIファイルから数値データを高速読み出し
□投稿者/ KOZ (275回)-(2022/07/10(Sun) 22:36:08)
No100193 (KOZ) に返信
> とりあえず、こんな形でメソッドを作ってみましたが、どうなんでしょうね?

ということで FIGHT!

Option Strict On

Imports System.Text

Module Module1

    Sub Main()
        Dim Bytes As Byte() = Encoding.ASCII.GetBytes("123.456")
        Watch(Sub()
                  Dim value As Single = BytesToSingle(Bytes)
              End Sub)
        Watch(Sub()
                  Dim value As Single = CSng(Val(Encoding.ASCII.GetString(Bytes)))
              End Sub)
    End Sub

    Private Sub Watch(action As Action)
        Dim sw As New Stopwatch()
        sw.Start()
        For i As Integer = 1 To 1000000
            action.Invoke()
        Next
        sw.Stop()
        Console.WriteLine("{0} ms", sw.ElapsedMilliseconds)
    End Sub

    Public Function BytesToSingle(value As Byte()) As Single
        Dim summary As Long
        Dim decimalPoint As Integer = value.Length - 1
        For i As Integer = 0 To value.Length - 1
            If value(i) = &H2E Then
                decimalPoint = i
            Else
                summary = summary * 10 + value(i) - &H30
            End If
        Next
        Return CSng(summary / (10 ^ (value.Length - decimalPoint - 1)))
    End Function

End Module

Debug ビルドだと

120 ms くらい
145 ms くらい

Release ビルド+詳細コンパイルオプションの「整数オーバーフローのチェックを削除」をチェックして

60 ms くらい
145 ms くらい

「整数オーバーフローのチェックを削除」をチェックしなければ

70 ms くらい
145 ms くらい

こんなかんじです。チリツモで少しは効果があるかもしれません。

引用返信 編集キー/
■100195 / inTopicNo.9)  Re[6]: ASCIIファイルから数値データを高速読み出し
□投稿者/ ロット (4回)-(2022/07/10(Sun) 22:58:01)
わざわざ検証してくださり
ありがとうございます。

結局、二倍程度しか変わらないのですね。。


ASCII文字列だと255通りあるわけですが、
数値に限定すると10通りくらいしかないわけなので、
20倍くらいは高速化できないかなと思いました。

他の方でも構いませんので、
もし、もっと高速化できそうなアルゴリズムがあれば
教えていただけないでしょうか?

引用返信 編集キー/
■100196 / inTopicNo.10)  Re[7]: ASCIIファイルから数値データを高速読み出し
□投稿者/ 伝説のカレー (28回)-(2022/07/11(Mon) 00:00:41)
No100195 (ロット さん) に返信

byte[]で読み取っておいて変換部分をPLINQで並列化したらどうですかね
引用返信 編集キー/
■100197 / inTopicNo.11)  Re[7]: ASCIIファイルから数値データを高速読み出し
□投稿者/ KOZ (276回)-(2022/07/11(Mon) 01:23:04)
No100195 (ロット さん) に返信
> ASCII文字列だと255通りあるわけですが、
> 数値に限定すると10通りくらいしかないわけなので、
> 20倍くらいは高速化できないかなと思いました。

Encoding.ASCII(ASCIIEncoding クラス) が扱うのは 0x00 〜 0x7F です。
byte と Char の変換をしてるだけなので、扱う文字の種類の多さ、少なさには
まったく関係ありません。

> もし、もっと高速化できそうなアルゴリズムがあれば
> 教えていただけないでしょうか?

ロット さんが考えていた方法はどうなのですか?
検証のやり方はわかったと思いますので、測定してみてください。
引用返信 編集キー/
■100198 / inTopicNo.12)  Re[8]: ASCIIファイルから数値データを高速読み出し
□投稿者/ ぶなっぷ (287回)-(2022/07/11(Mon) 09:14:06)
最初の話からして、
String型を一文字づつ切り出すと、Char型になって、その実体はASCIIコードだって
ことまでは分かってそうですね。

であるなら、
 > If ByteValue = 48 then
とかやってる場合ではないです。if文は遅いです。

VBの文法が分からないので、以下C#ですが、
  Out  = ByteValue - (int)'0';
とすれば、整数演算だけで数値化できます。

その後のコードも分かってそうですので以下略。

if文と整数演算の速度差は大きいと思うのですが、
こちらでは試していません。一度、お試しあれ。

引用返信 編集キー/
■100209 / inTopicNo.13)  Re[3]: ASCIIファイルから数値データを高速読み出し
□投稿者/ radian (67回)-(2022/07/11(Mon) 13:18:18)
2022/07/11(Mon) 14:18:47 編集(投稿者)

No100189 (ロット さん) に返信
> ■No100188 (WebSurfer さん) に返信
>>■No100187 (ロット さん) に返信
>>
>>どこがボトルネックになっているかできる限り範囲を絞って調べることは
>>できませんか?
>
> ボトルネックになっているのは
> バイトデータをテキストデータに変換するところ
> で間違いありません。

本当に?
ディスクIOと変換、それぞれ掛かった時間を書いてください。

> 普通にStreamReaderのReadLineで一秒ずつ読み込んで

って書いてあるので、ディスクIOがボトルネックだと
File.ReadAllBytesで一括でByte配列に読み込んで処理するだけで
大分マシになりそうではありますが。
引用返信 編集キー/
■100214 / inTopicNo.14)  Re[1]: ASCIIファイルから数値データを高速読み出し
□投稿者/ banana (2回)-(2022/07/11(Mon) 16:27:33)
No100187 (ロット さん) に返信

対象のファイルが固定長の数値+改行になっているのなら、自分ならTextFieldParserを使うと思います。
プロパティの設定により、前後のスペースもTrimしてくれるので、便利なように思いますが、
やっぱり遅いんでしょうか?

あと、Val()関数によるSingle型への変換は遅いんじゃないかと。
引用返信 編集キー/
■100217 / inTopicNo.15)  Re[2]: ASCIIファイルから数値データを高速読み出し
□投稿者/ 魔界の仮面弁士 (3432回)-(2022/07/11(Mon) 17:39:27)
2022/07/11(Mon) 17:55:11 編集(投稿者)

No100187 (ロット さん) に返信
> 各行に数値データが格納された大きなファイルサイズのテキストファイルがあり
> これをVB.NETを使って、Single配列に読み出したいと考えています。
内容によっては、出現頻度の高いデータを辞書化しておくことで
変換コストが下がる可能性もあります。効果があるか逆効果になるかは不明。

あるいはマルチコア CPU で並列変換させれば、高速化できるかもしれません。
「短い処理が多数ある状況」のままだと並列処理には向かないので、
「多数の変換処理」を 1 単位として CPU のコア数程度の数にチャンク化する必要はあるかも。


> 恐らく、バイトデータをテキストデータに変換するところが
> もっとも時間がかかっているはずなので、
テキスト変換に時間がかかっている…というのは、Single 型への解析処理を無しにして

・全件を StreamReader.ReadLine するのに要する時間(現在)
・File.ReadAllBytes での全体に全件列挙時に要する時間(文字列変換や改行判定なし)
・Stream.Read で一定サイズごとに分割しながら全部バイナリのままで読み取った場合の時間

を比較してみた、ということでしょうか。


メモリ量が十分にある場合には、
・File.ReadLines での全件列挙時に要する時間
・File.ReadAllLines での全件列挙時に要する時間
なども候補に挙がってくるところですが、巨大なメモリの確保と解放というのも
それなりにコストがかかるものなので、いずれにせよ計測が大事ですね。



No100191 (ロット さん) に返信
> 普通にStreamReaderのReadLineで一秒ずつ読み込んで
これは「一行ずつ」の誤りですよね。
1000ミリ秒単位で区切って読み込んでいるとは思えないので。



No100198 (ぶなっぷ さん) に返信
> VBの文法が分からないので、以下C#ですが、
> Out = ByteValue - (int)'0';
> とすれば、整数演算だけで数値化できます。
C# だったら
  Out = (int)ByteValue - (int)'0';
じゃないんでしたっけ…?

と思ったら、実際は
  Out = ByteValue - '0';
だけで十分なんですね。※unchecked を付与するかはお好みで。

Char を Int32 に直接キャストできるのは、C# や IL の場合の話ですが、
VB は言語仕様上 NG だったりします。この場合には
 Out = CInt(ByteValue) - 48
または
 Out = CInt(ByteValue - 48)
と書くことで、提示された内容に近い結果になるかと思います。


C# の場合は、Char を Int32 にキャストできるのですが、
VB の場合は AscW 関数を挟まないと変換できず、文法エラーになります。
>> エラー BC32006
>> 'Char' 値を 'Integer' に変換することはできません。
>> 文字を Unicode 値として扱うには 'AscW' を、
>> 数字として扱うには 'Val' を使用してください。

ということで、このケースでは
 Out = ByteValue - AscW("0"c)
と書くことになるでしょう。そしてこの場合、最適化されて
 Out = CInt(ByteValue - 48)
に相当するコードとしてコンパイルされるはずです。
※「整数オーバーフローのチェックを削除」が off の場合の IL 命令は 0xDA (sub.ovf)
※「整数オーバーフローのチェックを削除」が on の場合の IL 命令は 0x59 (sub)



No100214 (banana さん) に返信
> あと、Val()関数によるSingle型への変換は遅いんじゃないかと。

Val 関数には、
 Val(Char) As Integer
 Val(String) As Double
があって、今回は恐らく後者の方でしょうね。

今回は As Single を求めるので、私も Val だと冗長には思えました。
型も違いますし、変換コストが若干高くついたりしないかな…と。

でも調べてみると、CSng(string) した場合も内部実装では Double へ変換していて、
それをその後でそれを Single へと縮小変換しているみたいです。
となると、実はそれほど変わらないかも…? (当方では未計測です)

一方、Single.Parse や Single.TryParse の場合は、直接 Single 変換しているっぽい。


そもそも Val と CSng と Single.Parse では、それぞれの変換仕様が
微妙に異なっているので、ロジックだけでは単純比較ができませんでした。
パフォーマンスの差は、実際に測定してみないと分からない所ですね。


ただいずれにせよ、 No100187 のように自前で Single 化しようとすれば、
かえって遅くなってしまうかと思います。Single への変換処理については、
出来る限り、ライブラリ側の変換機構を利用する方針の方が良いと思いますよ。


あとは、System.Text.Decoder クラスの Convert メソッドや
System.Text.Encoding クラスの GetString メソッドや、
Single 構造体の Parse メソッドには、パフォーマンス最適化を目的として
ReadOnlySpan(Of Byte) や ReadOnlySpan(Of Char) を受け付けるオーバーロードが追加されているので、
.NET Framework ではなく .NET (.NET Core) にすれば、ReadOnlySpan(Of ) のオーバーロードを
活かす書き方にできるかも…と思ったりもしましたが、そもそも Visual Basic では
ReadOnlySpan がサポートされないので、意味が無さそう。
引用返信 編集キー/
■100219 / inTopicNo.16)  Re[3]: ASCIIファイルから数値データを高速読み出し
□投稿者/ HattariB (12回)-(2022/07/11(Mon) 19:12:43)
アルゴリズムに着目するなら、KOZさんの実装が最適だと思います。言語関係無くです。
それでも“もっと速く”というのに対してのレスに、アルゴリズムよりも性能に着目した話題が出てきたので、
アルゴリズム的にはもう打ち止めって感じだと思います。

ピリオドがあるから、一律な数値変換が出来ないし、
桁が不動だから行末迄読まなきゃいけないし、
KOZさんの実装以外に、どんな工夫があるんだろ?オイラはマヂわかんない。

C言語でのDuff's Deviceみたいな、条件付きながら、文法解釈のスキを突いた「とんがったロジック」をお求めだったら、
「偶然の気づき」みたいなのを待った方が良いんじゃないスかね。

オイラはこういった“切りの無いアイディア募集”みたいなスレは、
掲示板のスレにするよりも、AtCorderのお題にするとか、懸賞金かけて公募するとかした方が
良いものが集まると思います。
引用返信 編集キー/
■100221 / inTopicNo.17)  Re[3]: ASCIIファイルから数値データを高速読み出し
□投稿者/ くま (216回)-(2022/07/11(Mon) 19:27:36)
No100187 (ロット さん) に返信

ちょうど自分もこれと同じ事をしないといけなくなりまして
プログラムこれから作成するんですが

> 123.456
> 555.445
> 1.22234
> 6633.13
>・・・

そちらの環境で
・約何行で
・1行何文字で
・何秒かかっていて
・何秒以下にしたいか
教えてください。
引用返信 編集キー/
■100222 / inTopicNo.18)  Re[4]: ASCIIファイルから数値データを高速読み出し
□投稿者/ くま (217回)-(2022/07/11(Mon) 20:13:40)
私の環境でテスト
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim line As String = ""     '行テキスト
        Dim al As New ArrayList     '配列
        Dim count As Long = 0       '行数
        Const CSVPath = "D:\Visual Studio 2022\CSVConvTest\test.csv"

        ' 経過時間計測
        Dim sw As New System.Diagnostics.Stopwatch()
        sw.Start()

        Using sr As StreamReader = New StreamReader(CSVPath, Encoding.GetEncoding("Shift_JIS"))

            line = sr.ReadLine()
            Do Until line Is Nothing
                al.Add(CSng(line))
                line = sr.ReadLine()
                count += 1
            Loop

        End Using

        ' 経過時間計測
        sw.Stop()

        '経過時間を出力する
        Console.WriteLine("{0}msec {1}count", sw.ElapsedMilliseconds, count)
    End Sub

191msec 100000count
218msec 100000count
186msec 100000count
190msec 100000count
186msec 100000count
195msec 100000count
193msec 100000count
203msec 100000count
191msec 100000count
177msec 100000count

そんな遅くないんだけれども...

引用返信 編集キー/
■100223 / inTopicNo.19)  Re[5]: ASCIIファイルから数値データを高速読み出し
□投稿者/ 伝説のカレー (29回)-(2022/07/11(Mon) 21:21:12)
Dim lines = File.ReadAllLines("numbers.txt")
Dim sw As New System.Diagnostics.Stopwatch()

sw.Start()
Dim numbers = lines.AsParallel().Select(Function(x) Val(x)).ToArray()
sw.Stop()

Console.WriteLine("{0}msec", sw.ElapsedMilliseconds)


私の環境(.NET 4.8)ではこうなりました

CSng : 6514msec
Convert.ToSingle : 1313msec
Single.Parse : 1300msec
Val : 952msec
Val(AsParallel) : 315msec

Valって速いんですねー、環境依存かもしれないですけど
並列化するとコア数6のCPUで処理時間が1/3くらいにはなるっぽいです
引用返信 編集キー/
■100224 / inTopicNo.20)  Re[4]: ASCIIファイルから数値データを高速読み出し
 
□投稿者/ KOZ (281回)-(2022/07/11(Mon) 23:30:36)
No100221 (くま さん) に返信
> ちょうど自分もこれと同じ事をしないといけなくなりまして

元質問者のロットさんが出てこなくなっちゃいましたね。
くまさんのとこでも同じ案件があるってことは、どこか大手のシステム刷新があるのかもしれませんね。
ロットさんの案件では 数百MB あるらしいですが、くまさんはどのように聞いておられます?

> そちらの環境で
> ・約何行で
> ・1行何文字で
> ・何秒かかっていて
> ・何秒以下にしたいか
> 教えてください。

数秒、とか数百MB とか非常にあいまいなんですよね。
改行コード込みで 1行 9 バイトとすると 1,000万件で 100 MB 弱?

引用返信 編集キー/

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

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

管理者用

- Child Tree -