C# Splitの処理を高速化

# C# Splitの処理を高速化
##概要
 C# で文字分割を大量（秒間20万回くらいを数時間）に行う必要があったが、Split関数がどうも遅かったので、お手製で文字列分割することで、少しは高速になったのでその話です。

[この記事](https://qiita.com/d01tsumath/items/0554f1bd5d2086dfb720)で、C#の文字列操作の高速化の記事があったので、私も自分メモ的に残そうと思い投稿です。

 世の中にはより早い方法があるらしいのですが、ここが私の限界でした。。。。
 もっと早いのあるよって方がいたら、教えてください。

##前提条件
 何分割するか決まっている場合にのみ、正しく動作します。
 下のほうに記載したソースの場合、4個つのカンマで構成される文字のみ正しく動作するということです。
 
 こんな感じのデータ
 `"12345, D, 555555, 115151, EEEEEE"`

debugビルドだと、普通のSplitのほうが早くなります。
releaseビルドでやる必要があります。

##環境
[paiza.io](https://paiza.io/ja)  を使っています。
 C#をブラウザですぐに実行できるので、さっと処理を確かめたい時に便利です。

実行時間が2秒以内という制約があるので、2秒以内に収まる範囲の処理件数で、1回しか計測しません。

※マシンスペックにより、チューニングの仕方も違ったりするのでしょうが。。。
　だいたいのロジックを確認するだけなら、これでもいいはず。。。
####環境による違いについて
環境によりけりっぽいです。
@nogic1008 さんからコメントのある通り、[Try.NET](https://try.dot.net/)では、普通のスプリットの方が早いです。

高速化くしたかった環境が .net4.5.2だったのですが、
そこでは、お手製splitの方が高速でした。

##結果
200万回処理したときに、約300ミリ早くなりました。

```
loop count:2000000
nomalSplit:00:00:01.0473945
fastSplit:00:00:00.7556135
```

##文字分割を高速化した処理部分
###●処理の流れ
以下の流れで、処理しています。
1.区切り文字の位置を配列に保存
2.区切り文字の位置を利用し、Substringで文字抽出

###●試行錯誤する中での覚書
※いつか、全部比較版を載せたいところですが、、、、
・findより、微妙にループし区切り文字の位置を配列に入れたほうが早かった気がします
~~・i++するより、i = i + 1したほうが、ほんと微妙に早かった気がします。~~　←大嘘でした。すみません。
・以下のほうが、StringBuilderに格納していき、ToStringするより早かったと思います。

```C#
    private static string[] fastSplit(string str)
    {
        //結果を格納するための配列を生成する
        var splitedStr = new string[COL_NUM];
        //区切り文字を見つけた件数
        var count = 0;
        //区切り文字の位置を格納する配列
        var posi = new int[COUNT_OF_DELIMITER];
        
        //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
        //区切り文字であるなら、区切り文字の位置を保存
        for (var i = 0; i < str.Length; i = i + 1)
        {
            if (str[i] == DELIMITER)
            {
                posi[count] = i;
                if (count == COUNT_OF_DELIMITER - 1)
                    break;
                count = count + 1;
            }
        }
        
        //-----------------------------------------------------
        //Substringで文字列をスプリットしていく
        //1つ目の引数は、開始位置(0始まり)
        //2つ目の引数は、文字数
        //-----------------------------------------------------
        //※区切り文字を「カンマ」として説明を記載
        //・1列目のデータは、以下で抽出
        //　開始位置：0
        //    文字数：最初のカンマの位置（最初のカンマの位置＝最初の文字数になる）
        splitedStr[0] = str.Substring(0, posi[0]);
        //・2列目のデータは、以下で抽出（3列目以降も同じ感じ）
        //　開始位置：最初のカンマの位置 + 1
        //    文字数：次のカンマの位置 - 最初のカンマの位置 - 1
        splitedStr[1] = str.Substring(posi[0] + 1, posi[1] - posi[0] - 1);
        splitedStr[2] = str.Substring(posi[1] + 1, posi[2] - posi[1] - 1);
        splitedStr[3] = str.Substring(posi[2] + 1, posi[3] - posi[2] - 1);
        //・最後のデータは、以下で抽出
        //　開始位置：最後のカンマの位置
        //    文字数：指定なし（最後まで）
        splitedStr[4] = str.Substring(posi[3] + 1);
        return splitedStr;
    }
```

##さらなる高速化

ありがたいことに、コメントに高速化の案を頂いたので、
それらを比較してみました。

 [paiza.io](https://paiza.io/ja)の環境で、比較しているので、お使いの環境によっては、
必ずしも、比較結果通りになるとは限らないようです。

###結果
[paiza.io](https://paiza.io/ja)の環境では、@albireo さんから提案頂いた方法が一番速かったです。

下に記載していますが、「ローカルのVisualStudio2017 .Net Core 2.1 での計測」した場合も、
@albireo さんから提案頂いた方法が一番速かったです。

```
Loop Count:200000
 nomalSplit:00:00:00.1090672
 nomalSplit:00:00:00.1054340
  fastSplit:00:00:00.0765732
 fast2Split:00:00:00.0724998
nomalSplit2:00:00:00.1015977
```

### @albireo さん作成 ソース「fast2Split」
「区切り文字を見つけたらいったん配列に位置を格納して後でSubstringで切り出すより、区切りを見つけたらその場で切り出した方が効率よくないかな？」
という発想の様で、確かに速くなりました！

```C#
    private static string[] fast2Split(string str)
    {
      //結果を格納するための配列を生成する
      var splitedStr = new string[COL_NUM];
      //区切り文字を見つけた件数
      var count = 0;
      //切り出し開始位置
      var start = 0;
    
      //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
      //区切り文字であるなら、区切り文字の位置を保存
      for (var i = 0; i < str.Length; i++)
      {
        if (str[i] == DELIMITER)
        {
          //Substringで文字列をスプリットしていく
          splitedStr[count] = str.Substring(start, i - start);
          if (count + 1 == COUNT_OF_DELIMITER)
          {
            //最後のデータは、以下で抽出
            splitedStr[count + 1] = str.Substring(i + 1);
            break;
          }
          start = i + 1;
          count++;
        }
      }
      return splitedStr;
    }
```

### @muniel さん作成のソース 「nomalSplit2」
「.net core 2.1」 だと、普通にSplit速い、
paramsで毎回配列が作られるのを防ぐと速いってこと、みたいです。

[paiza.io](https://paiza.io/ja)や、私のローカルマシン(.net core 2.1)では、
あまり効果はなかったですが、特定の環境だと速いようです。

```C#
    static readonly char[] DELIMITERS = new char[] { DELIMITER };
    private static string[] nomalSplit2(string str)
    {
        return str.Split(DELIMITERS);
    }
```

##ローカルのVisualStudio2017 .Net Core 2.1 での計測
@muniel さんから、[paiza.io](https://paiza.io/ja)は、Mono環境なので、Split処理が遅いのでは？
と教えて頂いたので、ローカルマシンの「.Net Core 2.1」でも計測しました。

結論から言うと、私のローカルマシンでは、Releaseビルドの場合、お手製Splitのほうが早かったです。
※Debugビルドの場合は、お手製ビルドの方が遅いですが、それは.net4.5.2とかでも同じでした。

###計測方法について
@nogic1008 さんより、教えて頂いたので、以下の方法で計測します。
①計測前に1回処理を回す。
②10回くらい計測して、平均、最大、最少を出す。

デバッグモードだとお手製Splitの方が遅いので、
デバッグ、リリースの2種類で計測します。

###ローカルのVisualStudio2017 .Net Core 2.1 結果
####Releaseモード
【平均部門】
1位:fast2Split  295ミリ
2位:fastSplit   360ミリ
3位:nomalSplit  453ミリ
4位:nomalSplit2 457ミリ

【最大部門】
1位:fast2Split  345ミリ
2位:fastSplit   396ミリ
3位:nomalSplit  530ミリ
4位:nomalSplit2 541ミリ

【最小部門】
1位:fast2Split 227ミリ
2位:fastSplit 265ミリ
3位:nomalSplit 350ミリ
4位:nomalSplit2 385ミリ

```
Version: 4.0.30319.42000
---------------------------Check---------------------------
nomalSplit:12345
nomalSplit:D
nomalSplit:555555
nomalSplit:115151
nomalSplit:EEEEEE
 fastSplit:12345
 fastSplit:D
 fastSplit:555555
 fastSplit:115151
 fastSplit:EEEEEE
 fast2Split:12345
 fast2Split:D
 fast2Split:555555
 fast2Split:115151
 fast2Split:EEEEEE
---------------------------WarmUp--------------------------
 nomalSplit:00:00:00.7582664
  fastSplit:00:00:00.4453710
 fast2Split:00:00:00.3145498
nomalSplit2:00:00:00.5279528
---------------------------Start---------------------------
Loop Count:2000000
1回目
 nomalSplit:00:00:00.4922244
  fastSplit:00:00:00.3587043
 fast2Split:00:00:00.3316316
nomalSplit2:00:00:00.4347266
2回目
 nomalSplit:00:00:00.4510801
  fastSplit:00:00:00.3966643
 fast2Split:00:00:00.2736605
nomalSplit2:00:00:00.4440113
3回目
 nomalSplit:00:00:00.4323691
  fastSplit:00:00:00.3753283
 fast2Split:00:00:00.3134344
nomalSplit2:00:00:00.4772646
4回目
 nomalSplit:00:00:00.4941949
  fastSplit:00:00:00.3753482
 fast2Split:00:00:00.3459410
nomalSplit2:00:00:00.5416430
5回目
 nomalSplit:00:00:00.3982180
  fastSplit:00:00:00.3892855
 fast2Split:00:00:00.3071757
nomalSplit2:00:00:00.4579428
6回目
 nomalSplit:00:00:00.4836517
  fastSplit:00:00:00.3604454
 fast2Split:00:00:00.2599656
nomalSplit2:00:00:00.3852338
7回目
 nomalSplit:00:00:00.3505502
  fastSplit:00:00:00.2651866
 fast2Split:00:00:00.2270998
nomalSplit2:00:00:00.4426847
8回目
 nomalSplit:00:00:00.4628723
  fastSplit:00:00:00.3566661
 fast2Split:00:00:00.2776025
nomalSplit2:00:00:00.4873345
9回目
 nomalSplit:00:00:00.4371866
  fastSplit:00:00:00.3544766
 fast2Split:00:00:00.3323241
nomalSplit2:00:00:00.4397582
10回目
 nomalSplit:00:00:00.5304038
  fastSplit:00:00:00.3707582
 fast2Split:00:00:00.2867147
nomalSplit2:00:00:00.4599572
 nomalSplit--------------------------------
平均(ミリ秒):     453.27511
最大(ミリ秒):     530.4038
最少(ミリ秒):     350.5502
  fastSplit--------------------------------
平均(ミリ秒):     360.28635
最大(ミリ秒):     396.6643
最少(ミリ秒):     265.1866
 fast2Split--------------------------------
平均(ミリ秒):     295.55499
最大(ミリ秒):     345.941
最少(ミリ秒):     227.0998
nomalSplit2--------------------------------
平均(ミリ秒):     457.05567
最大(ミリ秒):     541.643
最少(ミリ秒):     385.2338
```
####Debugモード
```
Version: 4.0.30319.42000
Is DEBUG
---------------------------Check---------------------------
nomalSplit:12345
nomalSplit:D
nomalSplit:555555
nomalSplit:115151
nomalSplit:EEEEEE
 fastSplit:12345
 fastSplit:D
 fastSplit:555555
 fastSplit:115151
 fastSplit:EEEEEE
 fast2Split:12345
 fast2Split:D
 fast2Split:555555
 fast2Split:115151
 fast2Split:EEEEEE
---------------------------WarmUp--------------------------
 nomalSplit:00:00:00.9739630
  fastSplit:00:00:00.8629389
 fast2Split:00:00:00.7798239
nomalSplit2:00:00:00.5045547
---------------------------Start---------------------------
Loop Count:2000000
1回目
 nomalSplit:00:00:00.5979602
  fastSplit:00:00:00.7363702
 fast2Split:00:00:00.6911465
nomalSplit2:00:00:00.5020473
2回目
 nomalSplit:00:00:00.6521156
  fastSplit:00:00:00.7206363
 fast2Split:00:00:00.6235076
nomalSplit2:00:00:00.5577866
3回目
 nomalSplit:00:00:00.4520174
  fastSplit:00:00:00.7953817
 fast2Split:00:00:00.6193398
nomalSplit2:00:00:00.5030806
4回目
 nomalSplit:00:00:00.5479323
  fastSplit:00:00:00.8133679
 fast2Split:00:00:00.7045433
nomalSplit2:00:00:00.5573005
5回目
 nomalSplit:00:00:00.5690903
  fastSplit:00:00:00.6965763
 fast2Split:00:00:00.7562962
nomalSplit2:00:00:00.6061496
6回目
 nomalSplit:00:00:00.5495139
  fastSplit:00:00:00.7080167
 fast2Split:00:00:00.6088300
nomalSplit2:00:00:00.5475067
7回目
 nomalSplit:00:00:00.5417170
  fastSplit:00:00:00.7547760
 fast2Split:00:00:00.6409854
nomalSplit2:00:00:00.6064478
8回目
 nomalSplit:00:00:00.5446050
  fastSplit:00:00:00.7173445
 fast2Split:00:00:00.6388982
nomalSplit2:00:00:00.5174052
9回目
 nomalSplit:00:00:00.5890863
  fastSplit:00:00:00.8855495
 fast2Split:00:00:00.6835102
nomalSplit2:00:00:00.4958474
10回目
 nomalSplit:00:00:00.5653307
  fastSplit:00:00:00.7017383
 fast2Split:00:00:00.7640299
nomalSplit2:00:00:00.6352337
 nomalSplit--------------------------------
平均(ミリ秒):      560.93687
最大(ミリ秒):      652.1156
最少(ミリ秒):      452.0174
  fastSplit--------------------------------
平均(ミリ秒):      752.97574
最大(ミリ秒):      885.5495
最少(ミリ秒):      696.5763
 fast2Split--------------------------------
平均(ミリ秒):      673.10871
最大(ミリ秒):      764.0299
最少(ミリ秒):      608.83
nomalSplit2--------------------------------
平均(ミリ秒):      552.88054
最大(ミリ秒):      635.2337
最少(ミリ秒):      495.8474
```

##ソース（全部）
###[paiza.io](https://paiza.io/ja)
多分、[paiza.io](https://paiza.io/ja)に貼り付ければ、そのまま動くはず。。。

```C#
using System.Text;
using System;

public class Split{
    public static void Main(){
        //スプリットの対象文字
        var splitTargets = new string[]{"12345,D,555555,115151,EEEEEE"};

        //正しくスプリットできているかチェックする
        Console.WriteLine("---------------------------Check---------------------------");
        Check("nomalSplit", nomalSplit, splitTargets);
        Check(" fastSplit", fastSplit, splitTargets);
        Check(" fast2Split", fast2Split, splitTargets);

        //スプリットの速度計測
        Console.WriteLine("---------------------------Start---------------------------");
        Console.WriteLine("Loop Count:{0}", LOOP_COUNT);
        Sokutei(" nomalSplit", nomalSplit, splitTargets);
        Sokutei("  fastSplit", fastSplit, splitTargets);
        Sokutei(" fast2Split", fast2Split, splitTargets);
        Sokutei("nomalSplit2", nomalSplit2, splitTargets);
    }

    //スプリットの検証用メソッド
    private static void Check(string name, Func<string, string[]> splitFunc, string[] splitTargets)
    {
        foreach(var text in splitTargets)
        {
            var splitedValues = splitFunc(text);
            foreach(var val in splitedValues)
            {
                Console.WriteLine("{0}:{1}", name, val);
            }
        }
    }

    //速度図るためのカウント
    private const int LOOP_COUNT = 200000;
    //スプリットの速度計測用メソッド
    private static void Sokutei(string name, Func<string, string[]> splitFunc, string[] splitTargets)
    {
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        for (int i = 0; i < LOOP_COUNT; i++)
        {
            foreach(var text in splitTargets)
            {
                splitFunc(text);
            }
        }
        sw.Stop();
        Console.WriteLine("{0}:{1}", name, sw.Elapsed.ToString());
    }

    //区切り文字
    private const char DELIMITER = ',';
    //何個に区切るか
    private const int COL_NUM = 5;
    //区切り文字の数
    private const int COUNT_OF_DELIMITER = COL_NUM - 1; 

    //通常のスプリット
    private static string[] nomalSplit(string str)
    {
        return str.Split(DELIMITER);
    }

    //お手製スプリット
    private static string[] fastSplit(string str)
    {
        //結果を格納するための配列を生成する
        var splitedStr = new string[COL_NUM];
        //区切り文字を見つけた件数
        var count = 0;
        //区切り文字の位置を格納する配列
        var posi = new int[COUNT_OF_DELIMITER];

        //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
        //区切り文字であるなら、区切り文字の位置を保存
        for (var i = 0; i < str.Length; i = i + 1)
        {
            if (str[i] == DELIMITER)
            {
                posi[count] = i;
                if (count == COUNT_OF_DELIMITER - 1)
                    break;
                count = count + 1;
            }
        }

        //-----------------------------------------------------
        //Substringで文字列をスプリットしていく
        //1つ目の引数は、開始位置(0始まり)
        //2つ目の引数は、文字数
        //-----------------------------------------------------
        //※区切り文字を「カンマ」として説明を記載
        //・1列目のデータは、以下で抽出
        //　開始位置：0
        //    文字数：最初のカンマの位置（最初のカンマの位置＝最初の文字数になる）
        splitedStr[0] = str.Substring(0, posi[0]);
        //・2列目のデータは、以下で抽出（3列目以降も同じ感じ）
        //　開始位置：最初のカンマの位置 + 1
        //    文字数：次のカンマの位置 - 最初のカンマの位置 - 1
        splitedStr[1] = str.Substring(posi[0] + 1, posi[1] - posi[0] - 1);
        splitedStr[2] = str.Substring(posi[1] + 1, posi[2] - posi[1] - 1);
        splitedStr[3] = str.Substring(posi[2] + 1, posi[3] - posi[2] - 1);
        //・最後のデータは、以下で抽出
        //　開始位置：最後のカンマの位置
        //    文字数：指定なし（最後まで）
        splitedStr[4] = str.Substring(posi[3] + 1);
        return splitedStr;
    }
    
    private static string[] fast2Split(string str)
    {
      //結果を格納するための配列を生成する
      var splitedStr = new string[COL_NUM];
      //区切り文字を見つけた件数
      var count = 0;
      //切り出し開始位置
      var start = 0;
    
      //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
      //区切り文字であるなら、区切り文字の位置を保存
      for (var i = 0; i < str.Length; i++)
      {
        if (str[i] == DELIMITER)
        {
          //Substringで文字列をスプリットしていく
          splitedStr[count] = str.Substring(start, i - start);
          if (count + 1 == COUNT_OF_DELIMITER)
          {
            //最後のデータは、以下で抽出
            splitedStr[count + 1] = str.Substring(i + 1);
            break;
          }
          start = i + 1;
          count++;
        }
      }
      return splitedStr;
    }

    static readonly char[] DELIMITERS = new char[] { DELIMITER };
    private static string[] nomalSplit2(string str)
    {
        return str.Split(DELIMITERS);
    }
}

```

###ローカルのVisualStudio2017 .Net Core 2.1
```C#
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;

public class SplitSpeed
{
    public static void Main()
    {
        //スプリットの対象文字
        var splitTargets = new string[] { "12345,D,555555,115151,EEEEEE" };

        //環境
        Console.WriteLine("Version: {0}", Environment.Version.ToString());
#if DEBUG
        Console.WriteLine("Is DEBUG");
#endif
        //正しくスプリットできているかチェックする
        Console.WriteLine("---------------------------Check---------------------------");
        Check("nomalSplit", nomalSplit, splitTargets);
        Check(" fastSplit", fastSplit, splitTargets);
        Check(" fast2Split", fast2Split, splitTargets);
        //温める
        Console.WriteLine("---------------------------WarmUp--------------------------");
        Sokutei(" nomalSplit", nomalSplit, splitTargets);
        Sokutei("  fastSplit", fastSplit, splitTargets);
        Sokutei(" fast2Split", fast2Split, splitTargets);
        Sokutei("nomalSplit2", nomalSplit2, splitTargets);
        //合計のリセット
        ResultDic = new Dictionary<string, List<TimeSpan>>();
        //スプリットの速度計測
        Console.WriteLine("---------------------------Start---------------------------");
        Console.WriteLine("Loop Count:{0}", LOOP_COUNT);
        
        for (var i = 1; i <= 10; i++)
        {
            Console.WriteLine("{0}回目", i);
            Sokutei(" nomalSplit", nomalSplit, splitTargets);
            Sokutei("  fastSplit", fastSplit, splitTargets);
            Sokutei(" fast2Split", fast2Split, splitTargets);
            Sokutei("nomalSplit2", nomalSplit2, splitTargets);
        }
        foreach(var results in ResultDic)
        {
            Console.WriteLine("{0}--------------------------------", results.Key);
            Console.WriteLine("平均(ミリ秒):{0,12}", results.Value.Average(x => x.TotalMilliseconds));
            Console.WriteLine("最大(ミリ秒):{0,12}", results.Value.Max(x => x.TotalMilliseconds));
            Console.WriteLine("最少(ミリ秒):{0,12}", results.Value.Min(x => x.TotalMilliseconds));
        }
        Console.ReadLine();
    }

    //スプリットの検証用メソッド
    private static void Check(string name, Func<string, string[]> splitFunc, string[] splitTargets)
    {
        foreach (var text in splitTargets)
        {
            var splitedValues = splitFunc(text);
            foreach (var val in splitedValues)
            {
                Console.WriteLine("{0}:{1}", name, val);
            }
        }
    }

    private static Dictionary<string, List<TimeSpan>> ResultDic = new Dictionary<string, List<TimeSpan>>();

    //速度図るためのカウント
    private const int LOOP_COUNT = 2000000;
    //スプリットの速度計測用メソッド
    private static void Sokutei(string name, Func<string, string[]> splitFunc, string[] splitTargets)
    {
        System.Threading.Thread.Sleep(10);
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        for (int i = 0; i < LOOP_COUNT; i++)
        {
            foreach (var text in splitTargets)
            {
                splitFunc(text);
            }
        }
        sw.Stop();
        var elapsed = sw.Elapsed;

        //結果表示
        Console.WriteLine("{0}:{1}", name, elapsed.ToString());

        //結果の保存
        List<TimeSpan> results;
        if (ResultDic.TryGetValue(name, out results) == false)
        {
            results = new List<TimeSpan>();
            ResultDic.Add(name, results);
        }
        results.Add(elapsed);
    }

    //区切り文字
    private const char DELIMITER = ',';
    //何個に区切るか
    private const int COL_NUM = 5;
    //区切り文字の数
    private const int COUNT_OF_DELIMITER = COL_NUM - 1;

    //通常のスプリット
    private static string[] nomalSplit(string str)
    {
        return str.Split(DELIMITER);
    }

    //お手製スプリット
    private static string[] fastSplit(string str)
    {
        //結果を格納するための配列を生成する
        var splitedStr = new string[COL_NUM];
        //区切り文字を見つけた件数
        var count = 0;
        //区切り文字の位置を格納する配列
        var posi = new int[COUNT_OF_DELIMITER];

        //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
        //区切り文字であるなら、区切り文字の位置を保存
        for (var i = 0; i < str.Length; i = i + 1)
        {
            if (str[i] == DELIMITER)
            {
                posi[count] = i;
                if (count == COUNT_OF_DELIMITER - 1)
                    break;
                count = count + 1;
            }
        }

        //-----------------------------------------------------
        //Substringで文字列をスプリットしていく
        //1つ目の引数は、開始位置(0始まり)
        //2つ目の引数は、文字数
        //-----------------------------------------------------
        //※区切り文字を「カンマ」として説明を記載
        //・1列目のデータは、以下で抽出
        //　開始位置：0
        //    文字数：最初のカンマの位置（最初のカンマの位置＝最初の文字数になる）
        splitedStr[0] = str.Substring(0, posi[0]);
        //・2列目のデータは、以下で抽出（3列目以降も同じ感じ）
        //　開始位置：最初のカンマの位置 + 1
        //    文字数：次のカンマの位置 - 最初のカンマの位置 - 1
        splitedStr[1] = str.Substring(posi[0] + 1, posi[1] - posi[0] - 1);
        splitedStr[2] = str.Substring(posi[1] + 1, posi[2] - posi[1] - 1);
        splitedStr[3] = str.Substring(posi[2] + 1, posi[3] - posi[2] - 1);
        //・最後のデータは、以下で抽出
        //　開始位置：最後のカンマの位置
        //    文字数：指定なし（最後まで）
        splitedStr[4] = str.Substring(posi[3] + 1);
        return splitedStr;
    }

    private static string[] fast2Split(string str)
    {
        //結果を格納するための配列を生成する
        var splitedStr = new string[COL_NUM];
        //区切り文字を見つけた件数
        var count = 0;
        //切り出し開始位置
        var start = 0;

        //文字列をchar配列でループして、一文字ずつ区切り文字であるか判定
        //区切り文字であるなら、区切り文字の位置を保存
        for (var i = 0; i < str.Length; i++)
        {
            if (str[i] == DELIMITER)
            {
                //Substringで文字列をスプリットしていく
                splitedStr[count] = str.Substring(start, i - start);
                if (count + 1 == COUNT_OF_DELIMITER)
                {
                    //最後のデータは、以下で抽出
                    splitedStr[count + 1] = str.Substring(i + 1);
                    break;
                }
                start = i + 1;
                count++;
            }
        }
        return splitedStr;
    }

    static readonly char[] DELIMITERS = new char[] { DELIMITER };
    private static string[] nomalSplit2(string str)
    {
        return str.Split(DELIMITERS);
    }
}
```

##まとめ
ちょっとの差でも、回数多いとダメージでかいですよね。。。
何百万回と動作すると、少しずつ誤差が出ていく。。。

そして、「C#ってこんな言語だったっけ？」って思いました。。。
もっと早くなる方法があったら、教えてください。。。

本筋とは関係ないですが、[paiza.io](https://paiza.io/ja)のように、
すぐに動作を確認できる仕組みがあるって、ありがたいです。

ものすごく便利になったなと思いました。