0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C# UTF-8とUnicode(UTF-16 LE)とASCIIでReadAllLinesの処理時間を比較してみた(Windows10)

Last updated at Posted at 2019-10-29

やってみたこと

File.ReadAllLines()の入力テキストファイルを
Windows10 (C#でつくるexe含む)の内部エンコード(UTF-16 LE)に合わせたら
早くなるのか調べてみた。

結論:(少なくとも今回の条件・環境では)遅くなった。
おそらくファイルが大きくなるせい(※UTF-16は1文字を2byteで扱うので、ASCIIに対して2倍のファイルサイズになる。)

準備

今回の入力テキストファイルは、ASCIIで表現できる文字のみを対象とする。

入力ファイル(ASCII)の作成

下記で入力ファイルを作成。
暗号ライブラリの乱数生成を使用して、byte配列を生成し、
base64エンコーディングでASCII文字にする。
1行あたり78文字で改行はCRLFです。

ソースコード

using System;
using System.IO;
using System.Security.Cryptography;

class RandGen
{
    [STAThread]
    static void Main()
    {
        //Int32と同じサイズのバイト配列にランダムな値を設定する
        //byte[] bs = new byte[sizeof(int)];
        byte[] bs = new byte[78];
        string[] strs = new string[10000];
        var rng = new RNGCryptoServiceProvider();
        for ( int i=0;i<strs.Length;i++ ) {
            rng.GetBytes(bs);
            strs[i] = Convert.ToBase64String(bs);
        }
        File.WriteAllLines("tmp_ascii.txt", strs); // UTF-8 (Base64はASCII文字しか使ってないので、実質ASCII) で保存
    }
}

約1MB(1,060,000 byte)のファイルtmp_ascii.txtが出来上がりました。

入力ファイル(UTF-16 LE)の作成

Windows10(C#/.NET含む)の内部エンコードであるUTF-16(LE)のファイルの作成は、
上記で生成したASCIIのテキストファイルをSakuraエディタで開いてUnicodeでBOMなしを選択してtmp_utf16le.txtとして保存。
(ちなみにメモ帳で保存するとBOMありとなるようである。)

ちょうど2倍の2,120,000 byteのファイルができあがります。

計測用プログラム


using System;
using System.IO; // Fileクラス使用のため
using System.Diagnostics; // Stopwatchクラス使用のため
using System.Runtime.CompilerServices; // MethodImpl使用のため


class LoadTest
{
    const int TestTimes = 1001; // 中央値を取りたいので奇数回にする。

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void TestMeasureTime()
    {
        var measuredTicks = new long[TestTimes];
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for ( int i=0;i<TestTimes;i++ ) {
            // 明示的にGCを呼び出すことで、途中でGCが発生しにくくする。
            GC.Collect();
            GC.WaitForPendingFinalizers();
            
            // 計測開始
            sw.Restart();
            {
                // 下行のコメントアウトを外してLoadTimeTest_ascii.exe として保存 ・・・ 表中の「ASCII」
                // File.ReadAllLines("tmp_ascii.txt",System.Text.Encoding.ASCII);

                // 下行のコメントアウトを外してコンパイルし LoadTimeTest_no_opt.exe として保存 ・・・ 表中の「no_opt (UTF-8)」
                // File.ReadAllLines("tmp_ascii.txt");

                // 下行のコメントアウトを外してLoadTimeTest_utf16le.exe として保存 ・・・ 表中の「UTF-16 LE」
                // File.ReadAllLines("tmp_utf16le.txt",System.Text.Encoding.Unicode);
            }
            sw.Stop();

            // 計測結果を取得
            measuredTicks[i] = sw.ElapsedTicks;
        }

        for ( int i=0;i<TestTimes;i++ ) {
            Console.WriteLine(measuredTicks[i]);
        }

        // 中央値を取得
        Array.Sort(measuredTicks);
        Console.WriteLine("Median[Ticks]:" + measuredTicks[TestTimes/2].ToString());
        double medianInNs = (measuredTicks[TestTimes/2]*1000.0*1000.0*1000.0)/Stopwatch.Frequency;
        Console.WriteLine("Median[ns]:" + medianInNs.ToString());
    }

    [STAThread]
    static void Main()
    {
        TestMeasureTime();
    }
}

念のための確認

別のプログラム(下記に一部抜粋)でFile.ReadAllLinesで読み込んだ文字列が同一であることを確認済み。


const int TestTimes = 1;

string[] s1 = File.ReadAllLines("tmp_ascii.txt");
string[] s2 = File.ReadAllLines("tmp_ascii.txt",System.Text.Encoding.ASCII);
string[] s3 = File.ReadAllLines("tmp_utf16le.txt",System.Text.Encoding.Unicode);
Console.WriteLine(s1[0]);
Console.WriteLine(s2[0]);
Console.WriteLine(s3[0]);

測定結果

1回のexe実行で、1001回ファイルを読み込み、その中央値を計測した。
(平均だと他のアプリとかGCとかキャッシュとかの影響を受けやすそうなので、中央値を使用した。)

測定結果(中央値)
単位はms

exe実行回 ASCII no_opt
(UTF-8)
UTF-16 LE
1回目 4.0317 2.9995 4.5074
2回目 4.1147 3.0521 4.7191
3回目 4.2551 3.1007 4.3768

結論

何もしないの(no_opt)が一番早かった。

キャッシュ(?)されるまでの処理時間の傾向

「exe実行1回目」1 における、読み込み1001回のうち、最初の10回程度を取り出すと、下記のようになった。
ファイルとかもろもろ(?)がキャッシュされるのか、徐々に早くなる。

回目/1001回 ASCII no_opt
(UTF-8)
UTF-16 LE
1(i=0) 157871 100893 189679
2 108993 72674 117979
3 102807 69735 122891
4 102341 69005 116488
5 99202 69604 123357
6 92871 69354 102751
7 73894 69355 78776
8 60606 68971 64050
9 54991 68982 62492
10(i=9) 51871 53997 51491

実用上、同じファイルを連続で複数回読み込むシーンはあまり無いと思うので、
中央値や平均を見ても意味がないかもしれない。

1回読むだけなら

初回(i=0)の抜粋
中央値と違い、安定しない。
N数(サンプル数)が少ないが、何もしないの(no_opt)が一番早かった。

exe実行回 ASCII no_opt
(UTF-8)
UTF-16 LE
1回目 15.7871 10.0893 18.9679
2回目 18.3172 13.7212 19.1679
3回目 17.9538 14.0408 28.4987

実行した順序

実行した順序(コマンドプロンプトでの実行内容)

LoadTimeTest_ascii.exe > time_ascii.txt
LoadTimeTest_no_opt.exe > time_no_opt.txt
LoadTimeTest_utf16le.exe > time_utf16le.txt

LoadTimeTest_ascii.exe > time_ascii2.txt
LoadTimeTest_no_opt.exe > time_no_opt2.txt
LoadTimeTest_utf16le.exe > time_utf16le2.txt

LoadTimeTest_utf16le.exe > time_utf16le3.txt
LoadTimeTest_no_opt.exe > time_no_opt3.txt
LoadTimeTest_ascii.exe > time_ascii3.txt

おまけ

GCをとめる、ましな方法(一定メモリ容量を使用するまで停止できる):
.NET 4.6で追加された - GC.TryStartNoGCRegion~GC.EndNoGCRegionを使ってみる

(自分の環境では)デフォで入っているのは .NET 4.0.30319 なので、デフォ環境では使えない。
(※Visual Studio入れれば使える。)

デコード処理の実体

ILSpy.exeで追いかけてみたところ、下記がデコード処理の本体のよう。色々チェック処理が入っているのでそれが処理時間に影響しているのかも。
System.Text.ASCIIEncoding.GetChars
System.Text.UTF8Encoding.GetChars
System.Text.UnicodeEncoding.GetChars

  1. 実際にはデバッグのため動作させていたので、厳密には「1回目」ではない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?