在C#中,现在有多种方法来创建临时集合.

你可以新建一个array,希望垃圾收集器不会介意.

// Just create a new array
Span<int> array = new int[]{ 1, 2 };

unsafe上下文中,您可以使用stackalloc在堆栈上创建临时回收,从而减少垃圾收集器的负载.

// Create a new array on the stack
Span<int> stack = stackalloc int[2];
stack[0] = 1;
stack[1] = 2;

但现在,在最新的C#版本中,您也可以使用collection expression.

Span<int> span = [1, 2];

我对新集合表达式的工作原理有些困惑.尤其是现在,Visual Studio建议将堆栈分配转换为集合表达式.这意味着它们是等同的?

通常,我不会只是在性能关键部分中新建一个数组,并倾向于使用stackalloc.集合表达式的新语法非常方便.但我真的搞不懂它在引擎盖下是如何运作的.

C#specification建议,当分配给Span时,集合表达式可能会变成堆栈分配.但我不确定这一点目前是否/如何实施.我试着看过SharpLab gist室的IL代表.我真的不太习惯看IL.但是集合表达式和堆栈分配肯定不具有相同的IL表示.这是否意味着它目前不能将集合表达式转换为stackalLocs?

// stackalloc
ldc.i4.8
conv.u
localloc
ldc.i4.2
newobj instance void valuetype [System.Runtime]System.Span`1<int32>::.ctor(void*, int32)
stloc.s 4
ldloc.s 4
stloc.1
ldloca.s 1
ldc.i4.0
call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
ldc.i4.1
stind.i4
ldloca.s 1
ldc.i4.1
call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
ldc.i4.2
stind.i4

// collection expressions
ldloca.s 3
initobj valuetype '<>y__InlineArray2`1'<int32>
ldloca.s 3
ldc.i4.0
call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray2`1'<int32>, int32>(!!0&, int32)
ldc.i4.1
stind.i4
ldloca.s 3
ldc.i4.1
call !!1& '<PrivateImplementationDetails>'::InlineArrayElementRef<valuetype '<>y__InlineArray2`1'<int32>, int32>(!!0&, int32)
ldc.i4.2
stind.i4
ldloca.s 3
ldc.i4.2
call valuetype [System.Runtime]System.Span`1<!!1> '<PrivateImplementationDetails>'::InlineArrayAsSpan<valuetype '<>y__InlineArray2`1'<int32>, int32>(!!0&, int32)
stloc.2

有没有人更好地了解新的集合表达式在幕后是如何工作的,以及性能特征是什么,特别是在它生成垃圾的情况下?

推荐答案

我真的不太习惯看IL.

你不需要(在这种情况下),看看C# decompilation.


以下是实施细节,可能会更改.


反编译过程如下所示:

<>y__InlineArray2<int> buffer = default(<>y__InlineArray2<int>);
<PrivateImplementationDetails>.InlineArrayElementRef<<>y__InlineArray2<int>, int>(ref buffer, 0) = 1;
<PrivateImplementationDetails>.InlineArrayElementRef<<>y__InlineArray2<int>, int>(ref buffer, 1) = 2;
Span<int> span = <PrivateImplementationDetails>.InlineArrayAsSpan<<>y__InlineArray2<int>, int>(ref buffer, 2);

[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
    internal static Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(ref TBuffer buffer, int length)
    {
        return MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), length);
    }

    internal static ref TElement InlineArrayElementRef<TBuffer, TElement>(ref TBuffer buffer, int index)
    {
        return ref Unsafe.Add(ref Unsafe.As<TBuffer, TElement>(ref buffer), index);
    }
}

[StructLayout(LayoutKind.Auto)]
[InlineArray(2)]
internal struct <>y__InlineArray2<T>
{
    [CompilerGenerated]
    private T _element0;
}

这里主要感兴趣的是用InlineArrayAttribute标记的生成的<>y__InlineArray2<T>.这是C#12-inline arrays引入的新功能.

Inline arrays are used by the runtime team and other library authors to improve performance in your apps. Inline arrays enable a developer to create an array of fixed size in a struct type. A struct with an inline buffer should provide performance characteristics similar to an unsafe fixed size buffer.
You likely won't declare your own inline arrays, but you use them transparently when they're exposed as System.Span<T> or `System.ReadOnlySpan objects from runtime APIs.

基本上,它是在堆栈上分配的特殊类型,不应该导致额外的堆分配.

此外,当谈论性能时,最重要的是实际测量它.例如,我已经做了下一个小的合成基准测试(稍后将try 判断它有多大意义=):

[MemoryDiagnoser]
public class CollectionBench
{
    [Benchmark]
    public int NewArray()
    {
        Span<int> array = new int[]{ 1, 2 };

        return  Math.Max(array[0], array[1]);
    }
    
    [Benchmark]
    public unsafe int Stackalloc()
    {
        Span<int> stack = stackalloc int[2];
        stack[0] = 1;
        stack[1] = 2;

        return  Math.Max(stack[0], stack[1]);
    }
    
    [Benchmark]
    public int CollectionExpr()
    {
        Span<int> span = [1, 2];

        return Math.Max(span[0], span[1]);
    }
}

这给出了《On My Machine》(via BenchmarkDotNet):

Method Mean Error StdDev Gen0 Allocated
NewArray 3.4909 ns 0.0931 ns 0.1243 ns 0.0038 32 B
Stackalloc 0.9410 ns 0.0105 ns 0.0144 ns - -
CollectionExpr 0.2421 ns 0.0219 ns 0.0321 ns - -

正如您所看到的,没有为集合表达式进行额外的分配.但一定要根据您的实际代码/数据/硬件进行测量.

Csharp相关问答推荐

System.Text.Json Utf8JsonReader正在读取数组值两次

如何使用Automapper映射两个嵌套列表

Serilog SQL服务器接收器使用UTC作为时间戳

AutoMapper -如何为两个不同的用例设置单个映射?

在C#中使用in修饰符

C#相同名称的枚举方法和normal方法,参数类型不同

有没有一种方法可以在包含混合文本的标签中嵌入超链接?

我需要两个属性类吗

当我使用NET6作为目标框架时,为什么DotNet使用NET8作为MS包?

内部接口和类的DI解析

Azure函数中实体框架核心的依赖注入

MigraDoc文档

C#使用TextFieldParser读取.csv,但无法使用";0";替换创建的列表空条目

当索引和外键是不同的数据类型时,如何设置导航属性?

DateTime ToString()未以指定格式打印

将操作从编辑页重定向到带参数的索引页

我可以查看我们向应用程序洞察发送了多少数据吗?

在C#ASP.NET内核中使用INT AS-1进行控制器场景的单元测试

如何从原始图像到新创建的图像获得相同的特定 colored颜色 ,并且具有相同的 colored颜色 量和相同的宽度和高度?

实例化列表时的集合表达式是什么?