【问题标题】:Understanding Compiler Optimizations了解编译器优化
【发布时间】:2019-01-18 17:28:50
【问题描述】:

我试图了解编译器对非常简单的一段代码做了什么:

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,如果满足&gt; 1 条件将评估ImageWidth(尽管通过分配而不是比较)李>

现在,针对我的具体问题:

  • 为什么Release 配置使用赋值(int imageWidth = group.ImageWidth)而不是我原来的比较?执行作业是否更快?
  • 为什么Debug 配置完全改变了副作用的可能性?在此配置中,ImageHeightImageWidth 将始终被评估。

【问题讨论】:

  • 由于组在 Main() 中声明并且逻辑上在堆上,并且由于 MyMethod 是静态的并且在“全局”主变量组中传递...您正在为优化器提供免费通行证做一些大的假设。如果变量和代码块进出范围,您可能会看到更接近预期的结果。此外,您的 int 属性访问器中没有代码,这是另一个免费通行证。那是我的 2 位。
  • 请记住,C# 编译器本身并不倾向于进行大量“强”优化。很多更强大的优化都留给了 JIT 编译器。此外,您必须考虑到您使用的任何反编译器对您所看到的“之后”的影响与 C# 编译器一样大。如果你想知道 C# 编译器在做什么,你应该关注 IL 级别,而不是其他工具将 IL 重新解释为什么。
  • @Damien_The_Unbeliever 谢谢,这是有道理的,我会记住的。 IL 水平有点高出我的头...也许我应该学习这些天之一:)
  • 是的,IL 是非常“简单”或非常“复杂”的事物之一。这在很大程度上取决于你是否“得到”Stack Machines

标签: c# .net compiler-construction compiler-optimization


【解决方案1】:

对于第一个具体问题。当您在sharplab.io 上查看 IL 时 简单的分配是 1 个比较指令短。谁的“then”和“else”将指向相同的指令(在本例中为 IL_0012),因此调用函数不需要比较,两个 pop 就足够了。奇怪的是只加载将立即丢弃的 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

第二个具体问题。如果您在调试模式下查看同一页面上的 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

【讨论】:

  • “这样你就可以在调试器中查看它的结果”。那里的那条线非常有帮助。我很难理解为什么调试/发布之间的结果会有所不同。
猜你喜欢
  • 2014-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-07
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 2014-02-21
相关资源
最近更新 更多