2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C# .NET10 Preview3】参照型の小さな配列のスタック割り当て

Posted at

はじめに

.NET10 Preview3 関連記事の続きです。
リリースノート

今回はランタイム機能である参照型の小さな配列のスタック割り当て / Stack Allocation of Small Arrays of Reference Types を検証しようと思います。

Preview1 では値型の配列をスタックに割り当ててヒープアロケーションを削減していました。今回は参照型の配列もスタックに割り当てられるようになりました。

string[] words = {"Hello", "World!"};
foreach (var str in words)
    Console.WriteLine(str);

サンプルコード

サンプルコード
file record struct Person(string Name, int Age);

public class __StackAllocationOfSmallArraysOfReferenceTypesTest
{
    static void ClosedScope(Performance p)
    {
        p.AddTest("Array", () =>
        {
            string[] words =
                ["Hello", "World!", "C#", "dotnet", ".NET10"];
            var sum = 0;
            foreach (var n in words)
                sum += n.Length;
        });

        p.AddTest("ReadOnlySpan", () =>
        {
            ReadOnlySpan<string> words =
                ["Hello", "World!", "C#", "dotnet", ".NET10"];
            var sum = 0;
            foreach (var n in words)
                sum += n.Length;
        });
    }

    static void UseMethod(Performance p)
    {
        static int Sum(scoped ReadOnlySpan<string> words)
        {
            var result = 0;
            foreach (var n in words)
                result += n.Length;
            return result;
        }

        p.AddTest("Array", () =>
        {
            string[] words =
                ["Hello", "World!", "C#", "dotnet", ".NET10"];
            Sum(words);
        });

        p.AddTest("ReadOnlySpan", () =>
        {
            ReadOnlySpan<string> words =
                ["Hello", "World!", "C#", "dotnet", ".NET10"];
            Sum(words);
        });
    }

    static void PersonValueType(Performance p)
    {
        p.AddTest("Array", () =>
        {
            Person[] people = [
                new("Alice", 30),
                new("Bob", 25),
                new("Charlie", 35),
                new("Diana", 28),
                new("Eve", 22)
            ];
            var sum = 0;
            foreach (var n in people)
                sum += n.Age + n.Name.Length;
        });

        p.AddTest("ReadOnlySpan", () =>
        {
            ReadOnlySpan<Person> people = [
                new("Alice", 30),
                new("Bob", 25),
                new("Charlie", 35),
                new("Diana", 28),
                new("Eve", 22)
            ];
            var sum = 0;
            foreach (var n in people)
                sum += n.Age + n.Name.Length;
        });
    }
}

パフォーマンス計測

対象 CPU は x86
例のごとく x86 でしか最適化はかかりません

// Array
string[] words =
    ["Hello", "World!", "C#", "dotnet", ".NET10"];
var sum = 0;
foreach (var n in words)
    sum += n.Length;

// ReadOnlySpan
ReadOnlySpan<string> words =
    ["Hello", "World!", "C#", "dotnet", ".NET10"];
var sum = 0;
foreach (var n in words)
    sum += n.Length;
Test Score % CG0
.NET10 x86
Array 1,854,908 100.0% 0
ReadOnlySpan 1,840,533 99.2% 0
.NET9 x86
Array 1,689,021 100.0% 10
ReadOnlySpan 1,838,107 108.8% 0

実行環境: Windows11 x64(ランタイムは x86)
Score は高いほどパフォーマンスがよいです。
GC0 はガベージコレクション回数を表します(少ないほどパフォーマンスがよい)。

  • 最適化によりヒープアロケーションが削減されています
  • 最速であるとされるコレクション式と同水準のパフォーマンスです

配列を引数に渡した場合はヒープになる

static int Sum(scoped ReadOnlySpan<string> words)
{
    var result = 0;
    foreach (var n in words)
        result += n.Length;
    return result;
}

// Array
string[] words =
    ["Hello", "World!", "C#", "dotnet", ".NET10"];
Sum(words);

// ReadOnlySpan
ReadOnlySpan<string> words =
    ["Hello", "World!", "C#", "dotnet", ".NET10"];
Sum(words);
Test Score % CG0
.NET10 x86
Array 1,311,259 100.0% 8
ReadOnlySpan 1,496,933 114.2% 0
.NET9 x86
Array 1,329,545 100.0% 8
ReadOnlySpan 1,455,755 109.5% 0
  • 配列の参照が他のスコープに漏れる場合、最適化の対象外のようです
  • ReadOnlySpan 等の、読み取り専用で引数に渡した場合も最適化されません
  • scoped で参照が漏れない場合も最適化されません
  • この辺は興味深く、今後は最適化の余地がありそうです

参照型を含む値型の配列もスタックに割り当て

record struct Person(string Name, int Age);
Test Score % CG0
.NET10 x86
Array 1,794,758 100.0% 0
ReadOnlySpan 1,741,521 97.0% 0
.NET9 x86
Array 1,614,818 100.0% 16
ReadOnlySpan 1,686,389 104.4% 0
  • 最適化によりヒープアロケーションが削減されています
  • 参照型を含む値型の配列も、今回最適化の対象になったようです

おわりに

キキキキキキキタタタタタ─────((((゚゚゚∀∀゚゚゚゚)))))─────!!!!!!!!

.NET10 はスタック系の最適化が大変よろしいです。この最適化はランタイム機能のためそれをアテにしたコードを書くべきかはどうかですが、「コードは簡潔に保ってランタイムが最適化をかける」という、C# が当初から目指す方針に向かっているようです。

.NET10 Preview3

関連
【C#】.NET10 Preview1 キタ━━(゚∀゚)━━!!
【C# .NET10 Preview1】値型の配列をスタックに作成する最適化の検証
【C# .NET10 Preview2】参照型がスタックに置かれる最適化
【C# .NET10 Preview3】null 条件付き代入

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?