【问题标题】:How does a method with an absolute return path gets inlined?具有绝对返回路径的方法如何内联?
【发布时间】:2012-04-15 06:05:30
【问题描述】:

我主要使用 C# 进行开发,但我认为这个问题可能也适用于其他语言。
另外,这里似乎有很多代码,但问题很简单。 em>

据我了解,内联是编译器(在 C# 虚拟机的情况下)通过在调用方法的每个位置插入方法主体来替换方法调用。

假设我有以下程序:

static Main()
{        
    int number = 7;
    bool a;
    a = IsEven(number);
    Console.WriteLine(a);
}

...方法体IsEven:

bool IsEven(int n) 
{
    if (n % 2 == 0) // Two conditional return paths
        return true;
    else
        return false;
}

我可以理解内联方法后代码的样子:

static Main()
{
    int number = 7;
    bool a;
    if (number % 2 == 0)
        a = true;
    else
        a = false;
    Console.WriteLine(a); // Will print true if 'number' is even, otherwise false
}

一个明显简单而正确的程序。

但如果我稍微调整IsEven 的主体以包含绝对返回路径...

bool IsEven(int n)
{
    if (n % 2 == 0)
        return true;
    return false; // <- Absolute return path!
}

我个人在某些情况下更喜欢这种风格。一些折射工具甚至可能会建议我将第一个版本更改为看起来像这个 - 但是当我试图想象这种方法被内联时会是什么样子时,我被难住了。
如果我们内联方法的第二个版本:

static Main()
{
    int number = 7;
    bool a;
    if (number % 2 == 0)
        a = true;
    a = false;
    Console.WriteLine(a); // Will always print false!
}

要问的问题:
编译器/虚拟机如何处理内联具有绝对返回路径的方法?
这样的事情似乎极不可能真正阻止方法内联,所以我想知道如何处理这些事情。也许内联的过程没有这么简单?也许一个版本更有可能被 VM 内联?

编辑: 分析这两种方法(以及第一种方法的手动内联)在性能上没有差异,所以我只能假设这两种方法都内联并以相同或相似的方式工作(至少在我的 VM 上)。
此外,这些方法非常简单,看起来几乎可以互换,但是具有绝对返回路径的复杂方法可能更难以更改为没有绝对返回路径的版本。

【问题讨论】:

    标签: c# inline


    【解决方案1】:

    很难解释 JITter 内联时的作用 - 它不会更改 C# 代码来进行内联 - 它会(总是?)在生成的字节(编译版本)上工作 - 并且生成汇编代码(实际的机器代码字节)时所拥有的“工具”比 C#(或 IL)中的工具要细得多。

    也就是说,您可以通过考虑 break 关键字来了解它的工作原理,用 C# 术语。

    考虑每个不平凡的内联函数都包含在while (true) 循环(或do while(false) 循环)中的可能性——并且每个源返回都被转换为localVar = result; break; 语句集。然后你会得到这样的东西:

    static Main() 
    { 
        int number = 7; 
        bool a; 
        while (true)
        {
           if (number % 2 == 0) 
           {
              a = true; 
              break;
            }
            a = false; 
            break;
        }
    
        Console.WriteLine(a); // Will always print the right thing! Yey!
    }
    

    同样,在生成程序集时,您会看到生成了很多 jmps - 这些在道德上等同于 break 语句,但它们更加灵活(将它们视为匿名 goto 或其他东西)。

    所以你可以看到,jitter(以及任何编译成本机的编译器)手头有很多工具可以用来做“正确的事情”。

    【讨论】:

    • 顺便说一句:这种 while(true) break 机制对其他事情也很有用——我不经常使用它,但是当你有一组可能需要在订单,但可以在任何给定时刻停止,这很有用。同样,它在本地语言中用于模拟没有它的“finally”构造(我在某些项目中使用了很多)
    【解决方案2】:

    return 语句表示:

    • 结果值
    • 破坏局部变量(这适用于 C++,而不是 C#)在 C# 中,finally 块和 using 块中的 Dispose 调用将运行。
    • 跳出函数

    所有这些事情在内联后仍然发生。内联后跳转将是本地而不是跨函数,但它仍然存在。

    内联不像 C/C++ 宏那样进行文本替换。

    内联和文本替换之间的其他不同之处在于对同名变量的处理。

    【讨论】:

      【解决方案3】:

      cpu 执行的机器码是一种非常简单的语言。它没有 return 语句的概念,子程序有一个入口点和一个出口点。所以你的 IsEven() 方法是这样的:

      bool IsEven(int n)
      {
          if (n % 2 == 0)
              return true;
          return false;
      }
      

      需要通过抖动重写为类似于此的内容(无效的 C#):

      void IsEvent(int n) 
      {
          if (n % 2 == 0) {
             $retval = true;
             goto exit;
          }
          $retval = false;
      exit:
      }  // $retval becomes the function return value
      

      $retval 变量在这里可能看起来很假。不是,它是 x86 内核上的 EAX 寄存器。现在你会看到这段代码很容易内联,它可以直接移植到 Main() 的主体中。 $retval 变量可以通过简单的逻辑替换等同于 a 变量。

      【讨论】:

        猜你喜欢
        • 2010-09-05
        • 2021-05-23
        • 1970-01-01
        • 2017-10-26
        • 2015-10-16
        • 2023-03-16
        • 2011-07-23
        • 2011-03-19
        • 2012-10-26
        相关资源
        最近更新 更多