📝

カスタムスライスにはinline指定を

2024/06/19に公開

スライスとは

arr[start .. finish]の書式で部分配列を取得できます。startfinishは省略可能です。

これを拡張し、GetSlice()というメソッドが用意されていれば、任意のオブジェクトに対してobj[start .. finish]と記述できます。これをカスタムスライスと呼びます。どのような動作をさせるかはGetSlice()メソッド作成者の自由ですが、利用者からすれば配列と同等の動作が期待されるでしょう。
https://learn.microsoft.com/ja-jp/dotnet/fsharp/language-reference/slices

カスタムスライス例

GetSlice()のシグネチャはGetSlice(int option, int option) : 'Tとなります。例えば、Span<'T>に対してはこのような記述になるでしょうか。

type Span<'T> with
    member this.GetSlice(start, finish) =
        let start = match start with None -> 0 | Some start -> max 0 start
        match finish with
        | None        -> this.Slice(start)
        | Some finish -> this.Slice(start, min this.Length (finish + 1) - start)

性能懸念

スライスおよびカスタムスライスは配列のインデックスアクセスと同様に気軽に使われることが想定されるため、性能を考慮する必要があります。
問題は引数がint optionである点です。optionは参照型なため必ずGCアロケーションが発生するため、このままではカスタムスライスを気軽に使えなくなってしまいます。

そんな時はインライン展開をしましょう。 具体的にはinlineを指定します。

type Span<'T> with
    member inline this.GetSlice(start, finish) =
        let start = match start with None -> 0 | Some start -> max 0 start
        match finish with
        | None        -> this.Slice(start)
        | Some finish -> this.Slice(start, min this.Length (finish + 1) - start)

例えば span[..3] と記述した場合、span.GetSlice(None, Some 3)になります。インライン展開の上で定数式展開が行われ、span.Slice(0, min span.Length 4) にコンパイルされるため、int optionが生成されることはなくなり、GCアロケーションを回避できます。

結論

カスタムスライスのGetSlice()メソッドはinline指定をしましょう。

Discussion