はかせのラボ

私の頭の中を書いていく雑記ブログです

C# Span構造体なるものがあるらしい

あいさつ

どうも、はかせです。
今日は学校にいるときも家に帰ってからも
IL2CPPで吐いたソリューションのビルドが出来ず
あーでもないこーでもないとやってました。

結果一応ビルドはできたものの動確は
明日学校でという感じです。

そんなこんなで今日のネタがなく
ufcppさんのところでC#の勉強をしていたんですが、
そこでSpan構造体ってものを見かけました。

C#触り始めて何年目だよって言われそうですが
初見のものでした。

なので今回はそのメモ記事です。

Span構造体 is 何?

配列の一部分のビュー的なものらしいです。
要はこんな感じ
f:id:hakase0274:20190925210558p:plain

配列の一部分の参照を持ってくれるので
Spanでとってきたものを弄ったらその変更は
元の配列にまで伝わるみたいです。

使ってみる

とりあえずてけとーに動作確認用コード書いてみますた。

public class Program
{
    static void Main(string[] args)
    {
        //適当にint配列作る
        var numbers = Enumerable.Range(0, 10).ToArray();
        //まず中身確認
        foreach(var num in numbers)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine("\n");

        //Span使って3つめから3個の参照とる
        var numSpan = new Span<int>(numbers,3,3);
        //中身確認
        foreach(var num in numSpan)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine("\n");

        //適当に足してみる
        for(int i = 0;i < numSpan.Length; i++)
        {
            numSpan[i]++;
        }

        //反映確認
        foreach (var num in numbers)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine("\n");

        //まぁ一応Spanの方も確認
        foreach (var num in numSpan)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine("\n");

        Console.ReadLine();
    }
}

f:id:hakase0274:20190925212256p:plain

参照を使って弄るってC++のポインタチックでいいですね。

ref構造体?

Spanは構造体は構造体でも
ref構造体という特殊な構造体らしいです。

こいつは別名「stack-only型」なんて言われるらしく、
その名の通りスタック上にしか置けません

理由は単純でC#ではスタック上のものしか参照で使えないからです。
(というかおそらくGCがある言語は全て)

参照で使ったものがヒープ上だとGCでさよならバイバイする可能性が有って、
返ってくるときにバイバイされてると困るわけです。
参照という仕組みの関係上。

じゃあそうならないよう監視とかしとけばえーやんってなりそうですが、
このコストは割と馬鹿にならないそうで、
その辺のコスト削減のためにスタック上のもののみという制限を付けているらしいです。
(説明文からわかるように筆者も大分ふわっとした理解です)

あと似たような理屈で非同期系でも使えません。

さらに参照ってのは非同期同様伝搬してくもので、
ref付きでないところのメンバに持つこともできません。

つかクラスはrefつけれないんで事実上メンバに持つことは不可能です。
(クラスは参照型でヒープに置かれる)

Span is 使えない子?

何やらいろいろ制限があるくせして、
出来ることがぐんばつに増える気配もない。

さてはSpanは使えない子か?

その答えはNoです。

理由は
・中身が参照だから純粋に速い
・配列の一部分と言ってもT[]だけでなくstringやポインタなんかでも行ける

C#は割と何するにしても内部的にコピー作ったり、
受ける用の領域を取ったりと見えないところで
newしてます。

参照を直接ごにょる関係上
このnewが減るので速いって話です。

二個目はC++との連携や
unsafeコードを使ったライブラリなんかを使うときに使えます。

例えばこんな感じのunsafeコードがあったとして

public class Program
{
    static unsafe void Make(byte* p)
    {
        //なんか処理
    }

    static void Use()
    {
        var array = new byte[256];
        unsafe
        {
            fixed(byte* p = &array[0])
            {
                Clear(p);
            }
        }
    }
}

この場合作る側も使う側もunsafeが必要になります。
unsafeはその名の通り安全ではないですし、
そんなものを使ってる我々プログラマーの精神安全性も損ないます。

それをSpan使ってこんな感じにできます。

public class Program
{
    static void Make(Span<byte> span)
    {
        unsafe
        {
            fixed(byte* p = span)
            {
                //なんか処理
            }
        }
    }

    static void Use()
    {
        var array = new byte[256];
        Clear(new Span<byte>(array));
    }
}

unsafeが作る側の一部分に集約されました。
(ポインタと直変換できるのも何げ楽)

これで使う人の精神安全性を担保できますね。

あとがき

今回はSpan構造体の話でした。
出来たのがver7.2かららしいので
約2年前に登場したみたいです。
(私のC#にわかがバレてしまう)

普段使いするかって言われると微妙ですが、
特定場面ではスター選手になれそうな子ですね。
(C++との連携とかバイトコードもにょる時とか)

それでは今回はこの辺でノシ