[英]C#: Does Deconstruct(…) generate extra junk assignments in the compiled output?
I was checking whether or not deconstruction causes extra objects to be instantiated on the heap since I am doing something in an area where I need to have as little GC pressure as possible.我正在检查解构是否会导致在堆上实例化额外的对象,因为我在需要尽可能少的 GC 压力的区域做某事。 This is the code I was trying out:
这是我正在尝试的代码:
using System;
public struct Pair
{
public int A;
public int B;
public Pair(int a, int b)
{
A = a;
B = b;
}
public void Deconstruct(out int a, out int b)
{
a = A;
b = B;
}
}
public class Program
{
public static void Main()
{
Pair pair = new Pair(1, 2);
// Line of interest
(int a, int b) = pair;
Console.WriteLine(a + " " + b);
}
}
I ran this through SharpLab to see what C# is doing for me, and it does the following:我通过SharpLab运行了这个,看看 C# 为我做了什么,它做了以下事情:
public static void Main()
{
Pair pair = new Pair(1, 2);
Pair pair2 = pair;
int a;
int b;
pair2.Deconstruct(out a, out b);
int num = a;
int num2 = b;
Console.WriteLine(num.ToString() + " " + num2.ToString());
}
This answered my original question of whether or not I have to worry about extra allocations... but then even more interestingly, the release mode (since the above is debug) has:这回答了我最初的问题,即我是否必须担心额外的分配......但更有趣的是,发布模式(因为上面是调试)有:
public static void Main()
{
int a;
int b;
new Pair(1, 2).Deconstruct(out a, out b);
int num = a;
int num2 = b;
Console.WriteLine(num.ToString() + " " + num2.ToString());
}
However this can be reduced down to (this is me doing some extra variable pruning num
and num2
):然而,这可以减少到(这是我做一些额外的变量修剪
num
和num2
):
public static void Main()
{
int a;
int b;
new Pair(1, 2).Deconstruct(out a, out b);
Console.WriteLine(a.ToString() + " " + b.ToString());
}
This is a question of interest, since there is no way extra stack allocation of two integers is going to mean anything in terms of my program performance.这是一个有趣的问题,因为两个整数的额外堆栈分配对我的程序性能没有任何意义。 For fun though I tried looking at the IL of
Main
and get:虽然我尝试查看
Main
的 IL 并得到:
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2074
// Code size 54 (0x36)
.maxstack 3
.locals init (
[0] int32,
[1] int32,
[2] valuetype Pair,
[3] int32,
[4] int32
)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj instance void Pair::.ctor(int32, int32)
IL_0007: stloc.2
IL_0008: ldloca.s 2
IL_000a: ldloca.s 3
IL_000c: ldloca.s 4
IL_000e: call instance void Pair::Deconstruct(int32&, int32&)
IL_0013: ldloc.3
IL_0014: stloc.0
IL_0015: ldloc.s 4
IL_0017: stloc.1
IL_0018: ldloca.s 0
IL_001a: call instance string [System.Private.CoreLib]System.Int32::ToString()
IL_001f: ldstr " "
IL_0024: ldloca.s 1
IL_0026: call instance string [System.Private.CoreLib]System.Int32::ToString()
IL_002b: call string [System.Private.CoreLib]System.String::Concat(string, string, string)
IL_0030: call void [System.Console]System.Console::WriteLine(string)
IL_0035: ret
} // end of method Program::Main
and the JIT ASM is JIT ASM 是
Program.Main()
L0000: push ebp
L0001: mov ebp, esp
L0003: push edi
L0004: push esi
L0005: push ebx
L0006: sub esp, 0x20
L0009: lea edi, [ebp-0x28]
L000c: call 0x68233ac
L0011: mov eax, ebp
L0013: mov [ebp-0x14], eax
L0016: push 0x3
L0018: mov dword [ebp-0x20], 0x6cce29c
L001f: mov eax, esp
L0021: mov [ebp-0x1c], eax
L0024: lea eax, [0x146004df]
L002a: mov [ebp-0x18], eax
L002d: mov byte [esi+0x8], 0x0
L0031: call dword [0x6cce680]
L0037: mov byte [esi+0x8], 0x1
L003b: cmp dword [0x621e5188], 0x0
L0042: jz L0049
L0044: call 0x62023890
L0049: xor eax, eax
L004b: mov [ebp-0x18], eax
L004e: mov byte [esi+0x8], 0x1
L0052: mov eax, [ebp-0x24]
L0055: mov [esi+0xc], eax
L0058: lea esp, [ebp-0xc]
L005b: pop ebx
L005c: pop esi
L005d: pop edi
L005e: pop ebp
L005f: ret
However I'm a bit out of my league when it comes to the assembly part.但是,当涉及到装配部分时,我有点不合群。
Is there any intermediate there as seen as discussed earlier?如前所述,那里有任何中间体吗? I'm interested in if the
我感兴趣的是
int num = a;
int num2 = b;
were completely optimized out or not.是否完全优化。 I'm also interested in why the compiler would create intermediates in the release version (is there a reason?) or if it's a decompilation artifact from SharpLab.
我也对为什么编译器会在发布版本中创建中间体(有原因吗?)或者它是否是 SharpLab 的反编译工件感兴趣。
This is for a game engine where GC pauses are bad.
这适用于 GC 暂停很糟糕的游戏引擎。
As the cost of a GC pause maters, it is abundantly clear: You are doing realtime programming .由于 GC 暂停的成本很重要,因此非常清楚:您正在执行实时编程。 And I can only say that Realtime Programming and GC Memory Management do not mix.
而且我只能说Realtime Programming和GC Memory Management不要混用。
You might be able to fix this problem, but there is going to be another one.你也许可以解决这个问题,但还会有另一个问题。 And then another one after that.
然后是另一个。 And then more and more, until you finally realize you were on a dead end.
然后越来越多,直到你终于意识到自己走上了死胡同。 The sooner you realize that you are likely on a dead end, the more work you will be able to salvage.
您越早意识到自己可能处于死胡同,您就能挽救的工作就越多。
Historically game engines - especially the drawing code - are unmanaged code that uses direct memory management.从历史上看,游戏引擎——尤其是绘图代码——是使用直接 memory 管理的非托管代码。 .NET Code is bitness agnostic.
.NET 代码与位数无关。 But once you used professional drawing code, you were basically tied down to its bitness.
但是一旦你使用了专业的绘图代码,你基本上就被它的位数束缚住了。 However I can not tell if that was just inertia (we used that engine before and will not change it, just because the language/runtime did) or if it was important for performance.
但是我无法判断这是否只是惯性(我们之前使用过该引擎并且不会更改它,只是因为语言/运行时做了)还是它对性能很重要。
I also can not say how much of Unity drawing code uses unmanaged code.我也不能说有多少 Unity 绘图代码使用了非托管代码。 But as Unity games need to be built for specific platforms, I am going to assume: More than none.
但由于需要为特定平台构建 Unity 游戏,我将假设:多于没有。 So it might well be that not going into unmanaged code is impossible when doing game engines.
所以很可能在做游戏引擎时不进入非托管代码是不可能的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.