5

我正在尝试制作一个简短的 C# 片段来说明由于MSDN 中概述Assembly.GetCallingAssembly()的 JIT 内联而导致的行为变化。到目前为止,这是我的代码:

class Program
{
   static void Main(string[] args)
   {
       Console.WriteLine( GetAssembly().FullName );
       Console.ReadLine();
   }

   static Assembly GetAssembly()
   {
       return System.Reflection.Assembly.GetCallingAssembly();
   }
}

我在“Release”中构建并开始使用“Start without Debugging” - 此设置使此答案中的代码产生内联。我看到的结果是

ConsoleApplication2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

所以很明显GetAssembly()没有内联到Main(),否则我会看到mscorlib调用程序集。

我已经查看了所有内联标准,但我不明白为什么GetAssembly()不内联。

是否有可能知道为什么 JIT 编译器决定不内联调用?

4

3 回答 3

8

这是 .NET 3.5 中 Assembly.GetCallingAssembly() 的声明:

[MethodImpl(MethodImplOptions.NoInlining)]
public static Assembly GetCallingAssembly()
{
    StackCrawlMark lookForMyCallersCaller = StackCrawlMark.LookForMyCallersCaller;
    return nGetExecutingAssembly(ref lookForMyCallersCaller);
}

StackCrawlMark 枚举很有趣,当调用者将被内联时,“查找我的调用者调用者”无法正常工作。在 SSCLI20 source code for thread.cs 中有一条注释,其中声明了枚举:

声明此枚举类型的本地 var 并通过 ref 将其传递给需要进行堆栈爬取的函数将防止内联调用 [原文如此] 并将 ESP 点传递给堆栈爬取到

这与 GetCallingAssembly() 中发生的事情非常匹配,它是一个局部变量,并且确实由 ref 传递。不确定机制是什么,但抖动会产生一个名为 CORINFO_FLG_BAD_INLINEE 的方法属性。这反过来又强制调用 MethodDesc::SetNotInline()。这是一个猜测,这是非常模糊的。

于 2012-10-16T10:19:22.353 回答
3

再加上我的两分钱。不要依赖 JIT可能会做的事情来让您的程序正常运行。

禁止 JIT 内联方法的一些条件如下(取自此处

  • 大于 32 字节 IL 的方法将不会被内联。
  • 虚函数没有内联。
  • 具有复杂流控制的方法不会被内联。复杂流控制是除 if/then/else 之外的任何流控制;在这种情况下,切换或同时。
  • 包含异常处理块的方法不是内联的,尽管抛出异常的方法仍然是内联的候选方法。
  • 如果方法的任何形式参数是结构,则该方法将不会被内联。

仅仅因为 JIT可以内联该方法,并不意味着它一定会。将其与构建配置/运行时/操作系统组合之间的行为差​​异结合起来,嗯......你有它。

有关 .Net 3.5 SP1 JIT 的内联行为的更多信息在这里

于 2012-10-16T08:46:00.323 回答
1

实际上,您的调用Program.GetAssembly 内联到Program.Main. 但是您看不到区别,因为Program.GetAssemblyProgram.Main都定义在同一个名为ConsoleApplication2.

尽管您可以用另一种方式说明 JIT 内联:

using System;
using System.Diagnostics;

namespace A3
{
    public class Program
    {
        static void Main(string[] args)
        {
            StackFrame[] stackFrames = GetStackFrames();
            foreach (StackFrame stackFrame in stackFrames)
                Console.WriteLine(stackFrame.GetMethod().Name); // write method name

            Console.ReadLine();
        }

        //[MethodImpl(MethodImplOptions.NoInlining)]
        static StackFrame[] GetStackFrames()
        {
            StackTrace stackTrace = new StackTrace(); // get call stack
            return stackTrace.GetFrames(); // get method calls (frames)
        }
    }
}

如果没有 JIT 内联(例如,在 Debug 模式下或如果[MethodImpl(MethodImplOptions.NoInlining)]属性应用于GetStackFrames),它将至少向控制台写入两行:

  1. GetStackFrames
  2. Main

但如果发生内联,stackFrames将只包含一种方法:Main

更新

此外,您可以在此处阅读:调试和托管过程以及 Rawling 在他的评论中提到的:

Assembly.GetCallingAssembly().FullName 根据是否启用托管进程返回不同的结果。如果在启用托管进程的情况下调用 Assembly.GetCallingAssembly().FullName,它将返回 mscorlib。如果在禁用托管进程的情况下调用 Assembly.GetCallingAssembly().FullName,它会返回应用程序名称。

于 2012-10-16T08:37:16.800 回答