在.Net 8中转ImmutableArray性能优化

前言

.Net 8的预览版不知不觉已经发布5,这个月要发布PreView 6了,在往后的版本就开始转为RC版本,会暂停新特性或者新功能代码,有新特性的话,会加入到下一个大版本中.下面进入正文.数组在转ImmutableArray在.Net 8增加新的方式,这种方式性能更好.当如,如果对性能要求不高的话,可以在数组中直接使用ToImmutableArray转换为ImmutableArray.

测试代码

namespace CSharpBenchmarks.ArrayTest
{
    [MemoryDiagnoser]
    [DisassemblyDiagnoser(printSource: true)]
    public class ImmutableArrayTest
    {
        public People[] p1;


        [Params(100, 1000)]
        public int Count { get; set; }

        [GlobalSetup]
        public void Setup()
        {
            p1 = new People[Count];
            for (int i = 0; i < Count; i++)
            {
                p1[i] = new People { Id = i+1, Name = (i+1).ToString() };
            }
        }


        [Benchmark]
        public ImmutableArray<People> NewImmutableArrayTest1()
        {
            return p1.ToImmutableArray();
        }

        [Benchmark]
        public ImmutableArray<People> NewImmutableArrayTest2()
        {
            return Unsafe.As<People[], ImmutableArray<People>>(ref p1);
        }

#if NET8_0_OR_GREATER
        [Benchmark]
        public ImmutableArray<People> NewImmutableArrayTest3()
        {
            return ImmutableCollectionsMarshal.AsImmutableArray(p1);
        }
#endif

        [GlobalCleanup]
        public void Cleanup()
        {
            p1 = null;
        }
    }
}

使用BenchmarkDotNet进行性能基准测试,看看那个性能会好一些呢?

数组转不可变数组性能基准测试

如果你需要性能的话,在.Net 8 你可以使用ImmutableCollectionsMarshal(.Net 8新增)中AsImmutableArray,将数组转换不可变数组.在其他版本中,可以Unsafe.As转换不可变数组.不太注重性能的话,可以依然使用ToImmutableArray.

下面我们分别看一些生成的IL代码:

.method public hidebysig 
		instance valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<class CSharpBenchmarks.ArrayTest.People> NewImmutableArrayTest1 () cil managed 
	{
		.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
			01 00 25 00 00 00 4a 46 3a 5c 67 69 74 63 6f 64
			65 5c 4d 79 4c 65 61 72 6e 69 6e 67 5c 73 72 63
			5c 43 53 68 61 72 70 42 65 6e 63 68 6d 61 72 6b
			73 5c 41 72 72 61 79 54 65 73 74 5c 49 6d 6d 75
			74 61 62 6c 65 41 72 72 61 79 54 65 73 74 2e 63
			73 00 00
		)
		// Method begins at RVA 0x304a
		// Header size: 1
		// Code size: 12 (0xc)
		.maxstack 8

		// return p1.ToImmutableArray();
		IL_0000: ldarg.0
		IL_0001: ldfld class CSharpBenchmarks.ArrayTest.People[] CSharpBenchmarks.ArrayTest.ImmutableArrayTest::p1
		IL_0006: call valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<!!0> [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray::ToImmutableArray<class CSharpBenchmarks.ArrayTest.People>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
		IL_000b: ret
	} // end of method ImmutableArrayTest::NewImmutableArrayTest1

	.method public hidebysig 
		instance valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<class CSharpBenchmarks.ArrayTest.People> NewImmutableArrayTest2 () cil managed 
	{
		.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
			01 00 2b 00 00 00 4a 46 3a 5c 67 69 74 63 6f 64
			65 5c 4d 79 4c 65 61 72 6e 69 6e 67 5c 73 72 63
			5c 43 53 68 61 72 70 42 65 6e 63 68 6d 61 72 6b
			73 5c 41 72 72 61 79 54 65 73 74 5c 49 6d 6d 75
			74 61 62 6c 65 41 72 72 61 79 54 65 73 74 2e 63
			73 00 00
		)
		// Method begins at RVA 0x3057
		// Header size: 1
		// Code size: 17 (0x11)
		.maxstack 8

		// return Unsafe.As<People[], ImmutableArray<People>>(ref p1);
		IL_0000: ldarg.0
		IL_0001: ldflda class CSharpBenchmarks.ArrayTest.People[] CSharpBenchmarks.ArrayTest.ImmutableArrayTest::p1
		IL_0006: call !!1& [System.Runtime]System.Runtime.CompilerServices.Unsafe::As<class CSharpBenchmarks.ArrayTest.People[], valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<class CSharpBenchmarks.ArrayTest.People>>(!!0&)
		IL_000b: ldobj valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<class CSharpBenchmarks.ArrayTest.People>
		IL_0010: ret
	} // end of method ImmutableArrayTest::NewImmutableArrayTest2

	.method public hidebysig 
		instance valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<class CSharpBenchmarks.ArrayTest.People> NewImmutableArrayTest3 () cil managed 
	{
		.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
			01 00 32 00 00 00 4a 46 3a 5c 67 69 74 63 6f 64
			65 5c 4d 79 4c 65 61 72 6e 69 6e 67 5c 73 72 63
			5c 43 53 68 61 72 70 42 65 6e 63 68 6d 61 72 6b
			73 5c 41 72 72 61 79 54 65 73 74 5c 49 6d 6d 75
			74 61 62 6c 65 41 72 72 61 79 54 65 73 74 2e 63
			73 00 00
		)
		// Method begins at RVA 0x3069
		// Header size: 1
		// Code size: 12 (0xc)
		.maxstack 8

		// return ImmutableCollectionsMarshal.AsImmutableArray<People>(p1);
		IL_0000: ldarg.0
		IL_0001: ldfld class CSharpBenchmarks.ArrayTest.People[] CSharpBenchmarks.ArrayTest.ImmutableArrayTest::p1
		IL_0006: call valuetype [System.Collections.Immutable]System.Collections.Immutable.ImmutableArray`1<!!0> [System.Collections.Immutable]System.Runtime.InteropServices.ImmutableCollectionsMarshal::AsImmutableArray<class CSharpBenchmarks.ArrayTest.People>(!!0[])
		IL_000b: ret
	} // end of method ImmutableArrayTest::NewImmutableArrayTest3

在IL层面上,NewImmutableArrayTest2和NewImmutableArrayTest3代码还是有差异的.接着我们看一下汇编代码:

; CSharpBenchmarks.ArrayTest.ImmutableArrayTest.NewImmutableArrayTest2()
;             return Unsafe.As<People[], ImmutableArray<People>>(ref p1);
;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       mov       rax,[rcx+8]
       ret
; Total bytes of code 5


; CSharpBenchmarks.ArrayTest.ImmutableArrayTest.NewImmutableArrayTest3()
;             return ImmutableCollectionsMarshal.AsImmutableArray(p1);
;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       mov       rax,[rcx+8]
       ret
; Total bytes of code 5

这里就不贴NewImmutableArrayTest1的汇编代码,主要太长了,发现NewImmutableArrayTest2和NewImmutableArrayTest3最终生成的汇编代码是一样.


秋风 2023-07-02