我试图了解编译器对非常简单的代码段做了什么:
if (group.ImageHeight > 1 && group.ImageWidth > 1)
{ //No code exists between the braces
}
在 Debug
配置中编译后,然后反编译我看到了这个:
if (group.ImageHeight <= 1 || group.ImageWidth <= 1);
反编译 Release
配置结果
if (group.ImageHeight > 1)
{
int imageWidth = group.ImageWidth;
}
更完整(原始)代码:
public class Group
{
public int ImageHeight { get; set; }
public int ImageWidth { get; set; }
}
//The following belongs to a different project than `Group`
static void Main(string[] args)
{
Group group = new Group();
MyMethod(group);
}
static void MyMethod(Group group)
{
if (group.ImageHeight > 1 && group.ImageWidth > 1)
{
}
}
到目前为止,这是我的猜测和观察:
- 当我第一次开始这个时,我希望编译器完全放弃整个语句。我认为这不是因为对属性的评估可能会产生副作用。
- 我认为
group
类型属于我的解决方案中的另一个项目很重要。我这样说是因为编译器可能无法“知道”将来评估属性的副作用是什么。例如,我可以在编译后替换包含group
定义的 DLL。 - 在
Release
配置中,可能的副作用似乎与我的代码相同:评估ImageHeight
并且如果满足> 1
条件将评估ImageWidth
(尽管通过赋值而不是比较)
现在,对于我的具体问题:
- 为什么
Release
配置使用赋值 (int imageWidth = group.ImageWidth
) 而不是我原来的比较?运行作业是否更快? - 为什么
Debug
配置完全改变了副作用的可能性?在此配置中,将始终评估ImageHeight
和ImageWidth
。
最佳答案
对于第一个具体问题。当你在 sharplab.io 上查看 IL 时 简单的赋值是 1 条比较指令。谁的“then”和“else”将指向相同的指令(在本例中为 IL_0012),因此比较不需要调用函数,两次弹出就足够了。奇怪的是只加载将被立即丢弃的 Int32 常量 1。
如果 (group.ImageHeight > 1)
IL_0000: ldarg.0
IL_0001: callvirt instance int32 Group::get_ImageHeight()
IL_0006: ldc.i4.1
IL_0007: ble.s IL_0012
int imageWidth = group.ImageWidth;
IL_0009: ldarg.0
IL_000a: callvirt instance int32 Group::get_ImageWidth()
IL_000f: ldc.i4.1
IL_0010: pop
IL_0011: pop
IL_0012: ret
第二个具体问题。如果您在具有 Debug模式的同一页面上查看 IL,您会发现,代码是相同的,只有一些额外的调试指令和比较本身,因此您可以在调试器中查看它的结果。
IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt instance int32 Group::get_ImageHeight()
IL_0007: ldc.i4.1
IL_0008: ble.s IL_0015
IL_000a: ldarg.0
IL_000b: callvirt instance int32 Group::get_ImageWidth()
IL_0010: ldc.i4.1
IL_0011: cgt
IL_0013: br.s IL_0016
IL_0015: ldc.i4.0
IL_0016: stloc.0
// sequence point: hidden
IL_0017: ldloc.0
IL_0018: brfalse.s IL_001c
IL_001a: nop
IL_001b: nop
IL_001c: ret
关于c# - 了解编译器优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54258811/