【发布时间】:2021-07-18 05:37:18
【问题描述】:
我在玩 C# 时偶然发现了这个案例:
static int F(int n)
{
if (n == 1)
return 1;
return 1;
}
这会产生你所期望的:
<Program>$.<<Main>$>g__F|0_0(Int32)
L0000: mov eax, 1
L0005: ret
正如你所看到的,编译器理解 if 几乎没有用,并“删除”了它。
现在,让我们尝试添加更多ifs:
static int G(int n)
{
if (n == 1)
return 1;
if (n == 1)
return 1;
return 1;
}
现在这会生成以下ASM:
<Program>$.<<Main>$>g__G|0_1(Int32)
L0000: cmp ecx, 1 ; do we need this?
L0003: jne short L000b ; do we need this?
L0005: mov eax, 1
L000a: ret
L000b: mov eax, 1
L0010: ret
奇怪的是:当您添加 >= 5 分支时,它再次明白不需要它们。
static int H(int n)
{
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
return 1;
}
输出:
<Program>$.<<Main>$>g__H|0_2(Int32)
L0000: mov eax, 1
L0005: ret
问题
- 在第二种情况下生成额外指令是否有原因?
注意事项
- SharpLab 链接,如果你想玩的话。
- 这是
GCC (-O2)使用C生成的:
int
f(int n) {
if (n == 1)
return 1;
return 1;
}
int
g(int n) {
if (n == 1)
return 1;
if (n == 1)
return 1;
return 1;
}
int
h(int n) {
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
if (n == 1)
return 1;
return 1;
}
生成:
f:
mov eax, 1
ret
g:
mov eax, 1
ret
h:
mov eax, 1
ret
这里是Godbolt 链接。
- 函数的IL代码:
.method assembly hidebysig static
int32 '<<Main>$>g__F|0_0' (
int32 n
) cil managed
{
// Method begins at RVA 0x2052
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: pop
IL_0003: pop
IL_0004: ldc.i4.1
IL_0005: ret
} // end of method '<Program>$'::'<<Main>$>g__F|0_0'
.method assembly hidebysig static
int32 '<<Main>$>g__G|0_1' (
int32 n
) cil managed
{
// Method begins at RVA 0x2059
// Code size 12 (0xc)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: bne.un.s IL_0006
IL_0004: ldc.i4.1
IL_0005: ret
IL_0006: ldarg.0
IL_0007: ldc.i4.1
IL_0008: pop
IL_0009: pop
IL_000a: ldc.i4.1
IL_000b: ret
} // end of method '<Program>$'::'<<Main>$>g__G|0_1'
.method assembly hidebysig static
int32 '<<Main>$>g__H|0_2' (
int32 n
) cil managed
{
// Method begins at RVA 0x2066
// Code size 30 (0x1e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: bne.un.s IL_0006
IL_0004: ldc.i4.1
IL_0005: ret
IL_0006: ldarg.0
IL_0007: ldc.i4.1
IL_0008: bne.un.s IL_000c
IL_000a: ldc.i4.1
IL_000b: ret
IL_000c: ldarg.0
IL_000d: ldc.i4.1
IL_000e: bne.un.s IL_0012
IL_0010: ldc.i4.1
IL_0011: ret
IL_0012: ldarg.0
IL_0013: ldc.i4.1
IL_0014: bne.un.s IL_0018
IL_0016: ldc.i4.1
IL_0017: ret
IL_0018: ldarg.0
IL_0019: ldc.i4.1
IL_001a: pop
IL_001b: pop
IL_001c: ldc.i4.1
IL_001d: ret
} // end of method '<Program>$'::'<<Main>$>g__H|0_2'
【问题讨论】:
-
我不会想到一个非常有趣的原因。答案可能是“因为 JIT 编译器的这个特定实现细节:”。我很确定 5 只是一个神奇的数字。
-
是的,我认为这是有原因的(也许他们是故意的,但可能没有原因?)。
-
如果了解 IL 对这些情况的每一种情况会很有趣。这意味着 C# 编译器实际上是在进行优化(或不进行优化)还是 JIT。
-
@PMF 在那里添加了 IL 代码。
-
奇怪,我原以为 IL 已经优化了。但显然不是。
标签: c# assembly x86-64 compiler-optimization jit