为什么 .NET CLR 没有正确地内联它?
Why does the .NET CLR not inline this properly?
我 运行 发现 .NET JIT 编译器的内联行为不太理想。下面的代码去掉了它的上下文,但它演示了问题:
using System.Runtime.CompilerServices;
namespace HashTest
{
public class Hasher
{
private const int hashSize = sizeof(ulong) * 8;
public int SmallestMatch;
public int Offset;
public void Hash_Inline(ref ulong hash, byte[] data, int curIndex)
{
hash = (hash << 1) | (hash >> (hashSize - 1));
hash ^= data[curIndex];
if (curIndex < SmallestMatch)
{
ulong value = data[curIndex - SmallestMatch];
value = (value << Offset) | (value >> (hashSize - Offset));
hash ^= value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RotateLeft(ref ulong hash, int by)
{
hash = (hash << by) | (hash >> (hashSize - by));
}
public void Hash_FunctionCall(ref ulong hash, byte[] data, int curIndex)
{
RotateLeft(ref hash, curIndex);
hash ^= data[curIndex];
if (curIndex < SmallestMatch)
{
ulong value = data[curIndex - SmallestMatch];
RotateLeft(ref value, Offset);
hash ^= value;
}
}
}
}
这是它在运行时使用发布版本生成的汇编代码(只是函数的第一行,直到每个函数第二行的异或):
net472, inline code:
000007FE8E8E0A20 sub rsp,28h
000007FE8E8E0A24 rol qword ptr [rdx],1
net472, aggressive inlining:
000007FE8E8E09B0 sub rsp,28h
000007FE8E8E09B4 mov qword ptr [rsp+30h],rcx
000007FE8E8E09B9 mov ecx,r9d
000007FE8E8E09BC rol qword ptr [rdx],cl
net5.0, inline code:
000007FE67E56040 push rbp
000007FE67E56041 sub rsp,30h
000007FE67E56045 lea rbp,[rsp+30h]
000007FE67E5604A xor eax,eax
000007FE67E5604C mov qword ptr [rbp-8],rax
000007FE67E56050 mov qword ptr [rbp+10h],rcx
000007FE67E56054 mov qword ptr [rbp+18h],rdx
000007FE67E56058 mov qword ptr [rbp+20h],r8
000007FE67E5605C mov dword ptr [rbp+28h],r9d
000007FE67E56060 mov rcx,qword ptr [rbp+18h]
000007FE67E56064 rol qword ptr [rcx],1
net5.0, agressive inlining:
000007FE67E55B30 push rbp
000007FE67E55B31 sub rsp,30h
000007FE67E55B35 lea rbp,[rsp+30h]
000007FE67E55B3A xor eax,eax
000007FE67E55B3C mov qword ptr [rbp-8],rax
000007FE67E55B40 mov qword ptr [rbp+10h],rcx
000007FE67E55B44 mov qword ptr [rbp+18h],rdx
000007FE67E55B48 mov qword ptr [rbp+20h],r8
000007FE67E55B4C mov dword ptr [rbp+28h],r9d
000007FE67E55B50 mov rcx,qword ptr [rbp+10h]
000007FE67E55B54 mov rdx,qword ptr [rbp+18h]
000007FE67E55B58 mov r8d,dword ptr [rbp+28h]
000007FE67E55B5C call CLRStub[MethodDescPrestub]@7fe67e55558 (07FE67E55558h)
000007FE67E56010 push rbp
000007FE67E56011 mov rbp,rsp
000007FE67E56014 mov qword ptr [rbp+10h],rcx
000007FE67E56018 mov qword ptr [rbp+18h],rdx
000007FE67E5601C mov dword ptr [rbp+20h],r8d
000007FE67E56020 mov ecx,dword ptr [rbp+20h]
000007FE67E56023 mov rax,qword ptr [rbp+18h]
000007FE67E56027 rol qword ptr [rax],cl
000007FE67E5602A pop rbp
000007FE67E5602B ret
为什么它们不都生成相同的代码,即第一个示例中的 2 指令版本?有没有办法将“旋转”放在函数中而不是必须内联它?
编辑:我在发布的 RotateLeft 代码中发现了一个错误,它大大改进了生成程序集,但仍然存在问题。
函数Hash_Inline
和Hash_FunctionCall
不等价:
Hash_Inline
中的第一个语句旋转 1,但在 Hash_FunctionCall
中它旋转 curIndex
。
- 对于
RotateLeft
你可能的意思是:
private void RotateLeft(ref ulong hash, int by)
{
hash = (hash << by) | (hash >> (hashSize - by));
}
(注意:已编辑此问题以解决此问题)
如果修复这两个问题,JIT 编译器会为 .NET 5(但不是 .NET Framework)上的两个函数生成相同的本机代码:请参阅反汇编 here。
此外,如果您使用的是 .NET Core 3.0 或更高版本并且想要反汇编完全优化的代码,则需要调用该函数的次数足够多(以触发 tier 1 compilation)在进行反汇编之前,或使用 MethodImplOptions.AggressiveOptimization
.
我 运行 发现 .NET JIT 编译器的内联行为不太理想。下面的代码去掉了它的上下文,但它演示了问题:
using System.Runtime.CompilerServices;
namespace HashTest
{
public class Hasher
{
private const int hashSize = sizeof(ulong) * 8;
public int SmallestMatch;
public int Offset;
public void Hash_Inline(ref ulong hash, byte[] data, int curIndex)
{
hash = (hash << 1) | (hash >> (hashSize - 1));
hash ^= data[curIndex];
if (curIndex < SmallestMatch)
{
ulong value = data[curIndex - SmallestMatch];
value = (value << Offset) | (value >> (hashSize - Offset));
hash ^= value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RotateLeft(ref ulong hash, int by)
{
hash = (hash << by) | (hash >> (hashSize - by));
}
public void Hash_FunctionCall(ref ulong hash, byte[] data, int curIndex)
{
RotateLeft(ref hash, curIndex);
hash ^= data[curIndex];
if (curIndex < SmallestMatch)
{
ulong value = data[curIndex - SmallestMatch];
RotateLeft(ref value, Offset);
hash ^= value;
}
}
}
}
这是它在运行时使用发布版本生成的汇编代码(只是函数的第一行,直到每个函数第二行的异或):
net472, inline code:
000007FE8E8E0A20 sub rsp,28h
000007FE8E8E0A24 rol qword ptr [rdx],1
net472, aggressive inlining:
000007FE8E8E09B0 sub rsp,28h
000007FE8E8E09B4 mov qword ptr [rsp+30h],rcx
000007FE8E8E09B9 mov ecx,r9d
000007FE8E8E09BC rol qword ptr [rdx],cl
net5.0, inline code:
000007FE67E56040 push rbp
000007FE67E56041 sub rsp,30h
000007FE67E56045 lea rbp,[rsp+30h]
000007FE67E5604A xor eax,eax
000007FE67E5604C mov qword ptr [rbp-8],rax
000007FE67E56050 mov qword ptr [rbp+10h],rcx
000007FE67E56054 mov qword ptr [rbp+18h],rdx
000007FE67E56058 mov qword ptr [rbp+20h],r8
000007FE67E5605C mov dword ptr [rbp+28h],r9d
000007FE67E56060 mov rcx,qword ptr [rbp+18h]
000007FE67E56064 rol qword ptr [rcx],1
net5.0, agressive inlining:
000007FE67E55B30 push rbp
000007FE67E55B31 sub rsp,30h
000007FE67E55B35 lea rbp,[rsp+30h]
000007FE67E55B3A xor eax,eax
000007FE67E55B3C mov qword ptr [rbp-8],rax
000007FE67E55B40 mov qword ptr [rbp+10h],rcx
000007FE67E55B44 mov qword ptr [rbp+18h],rdx
000007FE67E55B48 mov qword ptr [rbp+20h],r8
000007FE67E55B4C mov dword ptr [rbp+28h],r9d
000007FE67E55B50 mov rcx,qword ptr [rbp+10h]
000007FE67E55B54 mov rdx,qword ptr [rbp+18h]
000007FE67E55B58 mov r8d,dword ptr [rbp+28h]
000007FE67E55B5C call CLRStub[MethodDescPrestub]@7fe67e55558 (07FE67E55558h)
000007FE67E56010 push rbp
000007FE67E56011 mov rbp,rsp
000007FE67E56014 mov qword ptr [rbp+10h],rcx
000007FE67E56018 mov qword ptr [rbp+18h],rdx
000007FE67E5601C mov dword ptr [rbp+20h],r8d
000007FE67E56020 mov ecx,dword ptr [rbp+20h]
000007FE67E56023 mov rax,qword ptr [rbp+18h]
000007FE67E56027 rol qword ptr [rax],cl
000007FE67E5602A pop rbp
000007FE67E5602B ret
为什么它们不都生成相同的代码,即第一个示例中的 2 指令版本?有没有办法将“旋转”放在函数中而不是必须内联它?
编辑:我在发布的 RotateLeft 代码中发现了一个错误,它大大改进了生成程序集,但仍然存在问题。
函数Hash_Inline
和Hash_FunctionCall
不等价:
Hash_Inline
中的第一个语句旋转 1,但在Hash_FunctionCall
中它旋转curIndex
。- 对于
RotateLeft
你可能的意思是:
private void RotateLeft(ref ulong hash, int by)
{
hash = (hash << by) | (hash >> (hashSize - by));
}
(注意:已编辑此问题以解决此问题)
如果修复这两个问题,JIT 编译器会为 .NET 5(但不是 .NET Framework)上的两个函数生成相同的本机代码:请参阅反汇编 here。
此外,如果您使用的是 .NET Core 3.0 或更高版本并且想要反汇编完全优化的代码,则需要调用该函数的次数足够多(以触发 tier 1 compilation)在进行反汇编之前,或使用 MethodImplOptions.AggressiveOptimization
.