おーみんブログ

C#, ASP.NET Core, Unityが大好きです。

Span<T>構造体を使ってみた。

はじめに

C#のお勉強としてufcppさんの未確認飛行Cを読んでいたらSpan構造体というものがあることを知ったので実際に使ってみました。

Span構造体

以下、ufcppさんの記事がとても分かりやすかったので引用させていただきます。

Span構造体(System名前空間)は、span (区間、範囲)という名前通り、連続してデータが並んでいるもの(配列など)の一定範囲を読み書きするために使う型です。

ufcpp.net

実際に使ってみる

本当はもっと深くSpan構造体の内容を知りたいのですが、ポインタやunsafeなど、ちょっと今の自分ではまだまだ分からない内容も多かったのでその部分は後程学習してしっかり身についてから書いていこうかなと思います。

今回は引用させていただいた記事内において、文字列をSubStringではなくSpanで参照した方が高速であることと配列の一部分を参照する際にArraySegmentよりもSpanで参照した方が高速であることを試してみます。 個人的にはこの2つについてはどちらもSubStringやArraySegmentを利用していたのでこれだけでも「おおー!!より効率的な処理になるんだ!いいじゃん!!!」と結構ワクワクしました(笑)

(もちろんSubStringとSpanのほうはコピーと直接参照の違いなので時と場に応じて使う必要がありますが...)

SubStringとSpan構造体

以下は100文字の文字列の4文字目から85文字分抜き出して変数に格納する時間をそれぞれ計っています(100万回ループ)。

static void Main(string[] args)
{
    var s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        var sub = s.Substring(3, 85);
    }
    sw.Stop();
    TimeSpan ts = sw.Elapsed;
    Console.WriteLine($"{ts.Seconds}秒 {ts.Milliseconds}ミリ秒");

    var sw2 = new System.Diagnostics.Stopwatch();
    sw2.Start();
    for (int i = 0; i < 1000000; i++)
    {
        var span = s.AsSpan().Slice(3, 85);
    }
    sw2.Stop();
    TimeSpan ts2 = sw2.Elapsed;
    Console.WriteLine($"{ts2.Seconds}秒 {ts2.Milliseconds}ミリ秒");
}
0秒 26ミリ秒  <-- SubString
0秒 13ミリ秒  <-- Span<T>

なるほど。確かにコピーしていない分Spanのほうが速いみたいです。

ArraySegmentとSpan構造体

以下は配列の3要素目から15要素取得して変数に格納する時間をそれぞれ計っています(100万回ループ)。

static void Main(string[] args)
{
    var array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };

    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    Console.WriteLine(new ArraySegment<int>(array, 2, 15));
    for (int i = 0; i < 1000000; i++)
    {
        var arySeg = new ArraySegment<int>(array, 2, 15);
    }
    sw.Stop();
    TimeSpan ts = sw.Elapsed;
    Console.WriteLine($"{ts.Seconds}秒 {ts.Milliseconds}ミリ秒");

    var sw2 = new System.Diagnostics.Stopwatch();
    sw2.Start();
    Console.WriteLine(new Span<int>(array, 2, 15).ToString());
    for (int i = 0; i < 1000000; i++)
    {
        var spanArr = new Span<int>(array, 2, 15);
    }
    sw2.Stop();
    TimeSpan ts2 = sw2.Elapsed;
    Console.WriteLine($"{ts2.Seconds}秒 {ts2.Milliseconds}ミリ秒");
}
0秒 7ミリ秒 <-- ArraySegment
0秒 4ミリ秒 <-- Span<T>構造体

おお~。こちらは誤差かな?と思って何度か試してみましたが、やはりSpanのほうがスピードが速かったです。

おわりに

まだまだSpanの良さの一部分しか知れていませんが、これだけでも個人的には視野が広がりました。 よりメリットを知れるようにC#自体の知識をより深めないとですね...(;´∀`) まずはポインタとunsafeとかとか勉強しないと...