dotnet/runtime

Perf-trap with `IBinaryInteger<T>.WriteLittleEndian`

オープン

#77,969 opened on 2022/11/07

 (6 件のコメント) (2 件のリアクション) (0 人の担当者)C# (5,445 件のフォーク)batch import
area-System.Numericshelp wanted

Repository metrics

Stars
 (17,886 個のスター)
PR merge metrics
 (平均マージ 12d 11h) (30d で 661 merged PRs)

説明

https://github.com/dotnet/runtime/pull/67939 added IBinaryInteger<T>.WriteLittleEndian as default interface method around TryWriteLittleEndian which is implemented by the numeric types.

The problem with the DIM is that value types get boxed, thus leading to unexpected allocations as encountered in https://github.com/dotnet/orleans/pull/8100#discussion_r1015299807.

Repro:

using System.Numerics;

Span<byte> span = stackalloc byte[128];
int value = 42;

for (int i = 0; i < 1_000_000; ++i)
{
    WriteLittleEndian(span, value);
}

int collections = GC.CollectionCount(0);
Console.WriteLine($"Collected: {(collections == 0 ? "no" : "yes")}");

static void WriteLittleEndian<T>(Span<byte> buffer, T value) where T : struct, IBinaryInteger<T>
{
    value.WriteLittleEndian(buffer);
}

Mitigation is

static void WriteLittleEndian<T>(Span<byte> buffer, T value) where T : struct, IBinaryInteger<T>
{
-    value.WriteLittleEndian(buffer);
+    value.TryWriteLittleEndian(buffer, out _);
}

But that is non-intuitive.

It would be better to either remove the DIM and implement that method on the concrete types (code duplication) or to make it a static virtual interface method. The latter would be a breaking change and for .NET 7 it's too late (I fear). Or there's some compiler magic to undo the boxing.

コントリビューターガイド