为什么我的方法调用Assembly.GetCallingAssembly()而不是JIT内联?

时间:2012-10-16 08:09:59

标签: c# visual-studio inline jit vshost.exe

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

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” - 这个设置使this answer的代码产生内联。我看到的结果是

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

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

我已经审核了所有内联标准,但我不明白为什么GetAssembly()不会被内联。

在某种程度上可以知道为什么JIT编译器决定不内联调用吗?

3 个答案:

答案 0 :(得分:8)

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

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

StackCrawlMark枚举很有意思,“查找我的调用者调用者”在调用者内联时无法正常工作。在CSSCL的SSCLI20源代码中有一条注释,其中枚举被声明:

  

声明此枚举类型的局部变量并将其通过ref传递给需要执行堆栈爬行的函数将同时阻止内联调用[sic]并将ESP点传递给堆栈爬行

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

答案 1 :(得分:3)

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

某些禁止JIT 能够内联方法的条件如下(取自here

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

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

有关.Net 3.5 SP1 JIT内联行为here

的更多信息

答案 2 :(得分: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内联(例如,在调试模式下或[MethodImpl(MethodImplOptions.NoInlining)]属性应用于GetStackFrames),它将至少向控制台写入两行:

  1. GetStackFrames
  2. Main
  3. 但如果内联发生,stackFrames将只包含一种方法:Main

    <强>更新

    另外,正如你可以在这里阅读:Debugging and the Hosting Process和罗林在评论中提到的那样:

      

    Assembly.GetCallingAssembly()。FullName返回不同​​的结果   取决于是否启用了托管过程。如果你打电话   Assembly.GetCallingAssembly()。FullName与托管进程   启用后,它返回mscorlib。如果你打电话   Assembly.GetCallingAssembly()。FullName与托管进程   禁用,它返回应用程序名称。