【发布时间】:2020-05-06 20:46:12
【问题描述】:
我正在检查解构是否会导致在堆上实例化额外的对象,因为我在需要尽可能少的 GC 压力的区域中做某事。这是我正在尝试的代码:
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);
}
}
我通过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());
}
这回答了我最初是否需要担心额外分配的问题......但更有趣的是,发布模式(因为上面是调试)有:
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());
}
但是这可以减少到(这是我做一些额外的变量修剪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());
}
这是一个有趣的问题,因为两个整数的额外堆栈分配对我的程序性能没有任何意义。为了好玩,虽然我尝试查看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
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
但是,在组装部分方面,我有点不合群。
如前所述,那里有任何中间体吗?我感兴趣的是
int num = a;
int num2 = b;
是否已完全优化。我还对为什么编译器会在发布版本中创建中间体(有原因吗?)或者它是否是 SharpLab 的反编译工件感兴趣。
【问题讨论】:
-
“我正在检查解构是否会导致额外的对象在堆上被实例化,因为我在一个需要尽可能少的 GC 压力的区域做某事”这听起来像一个 XY问题。你为什么有这个要求?与其尝试改变 GC 的作用,不如完全避开它,使用非托管内存?
-
@Christopher 这适用于 GC 暂停很糟糕的游戏引擎。除非我别无选择,否则我绝对不喜欢做非托管内存,尤其是当一个简单、更易读、更安全的替代方案可用时,除非我已经用尽所有选项。话虽如此,我宁愿将此线程专门针对手头的问题,而不是问题的前奏。
-
我理解您不愿意使用裸指针太好了。我学习了原生 C++ 编程。与此相比,.NET 简直是美梦成真。我永远不会选择 回到“处理裸指针”。对我来说,这就像玩手榴弹一样令人向往。但这可能是真的没有其他选择的情况。
-
即使你不能使用它,也可以看看 Unitys HPC (High Performance C#)。关于这个概念及其推理有一些很好的读物。