我缺少严肃的优化,因为JIT不会内联我的很多方法。
例如,让我们拥有以下代码:
static void Main(string[] args)
{
IsControl('\0');
}
public static bool IsControl(char c)
{
return ((c >= 0 && c <= 31) || (c >= 127 && c <= 159));
}
在JIT编译后生成以下内容:
0000001f xor ecx,ecx
00000021 call FFFFFFFFFFEC9760
00000026 mov byte ptr [rsp+20h],al
0000002a nop
0000002b jmp 000000000000002D
0000002d add rsp,38h
00000031 rep ret
请注意0000001f
是我设置断点的地方。你可以看到00000021
有一个电话,这是绝对错误的。为什么这么小的方法不适合内联?对于注释,这是通过优化编译的。
答案 0 :(得分:8)
除了使用提前的源代码或字节码转换在指令到达JIT之前内联指令之外,没有办法要求JIT编译器内联您的方法。
如果您的算法对微优化非常敏感,那么删除调用指令会带来显着的性能优势,那么您可以考虑用不同的语言重写代码的性能关键部分,从而为控制该行为提供更广泛的工具。根据您的问题的措辞,您似乎试图将C#强制进入一个设计为完全避免的问题空间。
答案 1 :(得分:5)
使用MethodImplAttribute
属性:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsControl(char c)
{
return ((c >= 0 && c <= 31) || (c >= 127 && c <= 159));
}
见
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.methodimplattribute.aspx
和
http://blogs.microsoft.co.il/sasha/2012/01/20/aggressive-inlining-in-the-clr-45-jit/
答案 2 :(得分:4)
.Net的抖动具有内置的启发式功能,有助于确定To Inline or not to Inline 。因为我找不到一个阻止内联的好理由(见下文),并且在4.5中可以通过AggressiveInlining
说服它,所以抖动可以内联,如果它想要的话,可能就是这样。引用:
如果内联使代码变小,那么它取代的调用就会很好。请注意,我们讨论的是NATIVE代码大小,而不是 IL代码大小(可能完全不同)。
执行特定呼叫站点的次数越多,它就越有利于inlning。因此,循环中的代码应该更多地内联 而不是循环中的代码。
- 醇>
如果内联公开了重要的优化,那么内联是更理想的。特别是具有值类型参数的方法 由于这样的优化,因此比正常受益更多 偏向内联这些方法是好的。
因此,给定内联的X86 JIT编译器使用的启发式算法 候选者。
如果方法未内联,请估算调用网站的大小。
估计呼叫站点的大小(如果它是内联的)(这是基于IL的估计,我们使用简单的状态机(Markov) 模型),使用大量实际数据创建,形成此估算逻辑)
计算乘数。默认情况下为1
如果代码处于循环中,则增加乘数(当前启发式循环将其变为5)
如果看起来结构优化会起作用,请增加乘数。
- 醇>
如果InlineSize&lt; = NonInlineSize * Multiplier执行内联。
以下是对我尝试深究这一点的描述,它可能会在类似的情况下帮助其他人。
我可以在.Net 4.5(包括x68和x64)上重现它,但我不知道为什么它没有内联,因为它没有inlining show stoppers像虚拟方法或消耗更多超过32个字节。它短30个字节:
.method public hidebysig static bool IsControl(char c) cil managed
{
// code size 30 (0x1e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: blt.s IL_0009
IL_0004: ldarg.0
IL_0005: ldc.i4.s 31
IL_0007: ble.s IL_001c
IL_0009: ldarg.0
IL_000a: ldc.i4.s 127
IL_000c: blt.s IL_001a
IL_000e: ldarg.0
IL_000f: ldc.i4 0x9f
IL_0014: cgt
IL_0016: ldc.i4.0
IL_0017: ceq
IL_0019: ret
IL_001a: ldc.i4.0
IL_001b: ret
IL_001c: ldc.i4.1
IL_001d: ret
} // end of method Program::IsControl
当启用AggressiveInlining
(您说你不能,因为你在.Net 3.5上)时,不仅内联调用,而且内联代码完全被删除 - 因为它应该,因为你不# 39; t使用返回值:
--- Program.cs --------------------------------------------
IsControl('\0');
00000000 ret
N.B。我不确定您是否知道除了使用发布构建模式之外,您have to
- 转到工具=&gt;选项=&gt;调试=&gt;常规并确保标记为“在模块加载时抑制JIT优化”的框未选中。
- 确保标记为“启用我的代码”的框未选中。
以便查看JIT优化代码。如果您不这样做,您将获得以下内容而不是上述单ret
声明:
--- Program.cs --------------------------------------------
IsControl('\0');
00000000 push rbp
00000001 sub rsp,30h
00000005 lea rbp,[rsp+30h]
0000000a mov qword ptr [rbp+10h],rcx
0000000e mov rax,7FF7F43335E0h
00000018 cmp dword ptr [rax],0
0000001b je 0000000000000022
0000001d call 000000005FAB06C4
00000022 xor ecx,ecx
00000024 call FFFFFFFFFFFFD3D0
00000029 and eax,0FFh
0000002e mov dword ptr [rbp-4],eax
00000031 nop
}
00000032 nop
00000033 lea rsp,[rbp]
00000037 pop rbp
00000038 ret
即使没有AggressiveInlining
,以下更短(而非等效)的btw方法也会被内联:
public static bool IsControl(char c)
{
return c <= 31 || c >= 127;
}
答案 3 :(得分:3)
当您进行微优化时,IsControl
方法应该看起来像下列之一,具体取决于c
值的(预期)实际分布:
public static bool IsControl2(char c)
{
return c <= 31 || (c >= 127 && c <= 159);
}
public static bool IsControl3(char c)
{
return c <= 159 && (c <= 31 || c >= 127);
}
它将删除对c >= 0
(minimum value of char is 0)的多余检查,在最坏的情况下将比较次数减少到3(尽管我还没有检查抖动是否足够聪明为了避免冗余检查,它还将方法的代码大小从30字节减少到26字节,这可能会影响抖动决定是否内联。
答案 4 :(得分:1)
Visual Studio 2017(及更早版本)调试器选项中有一个名为“Suppress JIT optimization on module load (Managed Only)”的复选框。如果选中此选项,则无论 Release 或 Debug build还是[MethodImpl(MethodImplOptions.AggressiveInlining)]
属性,都不会在调试器中运行任何方法内联。