编辑
未使用的cmp指令将导致NullPointerException。
这些奇怪的cmp [ecx],ecx指令在我的C#代码中是做什么的?
原帖(以下是更多编辑)
我试图理解JIT编译代码的方式。
在内存中,我有一个三个字符的字段。 在c ++中,为了比较两个这样的字段,我可以这样做:
return ((*(DWORD*)p) & 0xFFFFFF00) == ((*(DWORD*)q) & 0xFFFFFF00);
MSVC 2010 会生成类似于以下内容(根据记忆):
1 mov edx,dword ptr [rsp+8]
2 and edx,0FFFFFF00h
3 mov ecx,dword ptr [rsp]
4 and ecx,0FFFFFF00h
5 cmp edx,ecx
在C#中,我正在尝试找出如何尽可能接近它。我们的记录由许多1,2,3,4,5,6,7,8字节字段组成。我已经尝试了许多不同的方法,在c#中使用较小的结构体构建代表记录的较大结构体。我对汇编代码不满意。现在我正在尝试类似于以下内容的东西:
[StructLayout(LayoutKind.Sequential, Size = 3)]
public unsafe struct KLF3
{
public fixed byte Field[3];
public bool Equals(ref KLF3 r)
{
fixed (byte* p = Field, q = r.Field)
{
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
}
}
}
但是我有两个问题。第一个问题是编译器生成了很多看起来毫无用处的代码:
fixed (byte* p = Field, q = r.Field)
1 sub rsp,18h
2 mov qword ptr [rsp+8],0
3 mov qword ptr [rsp],0
4 cmp byte ptr [rcx],0
5 mov qword ptr [rsp+8],rcx
6 cmp byte ptr [rdx],0
7 mov qword ptr [rsp],rdx
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
8 mov rax,qword ptr [rsp+8]
9 mov edx,dword ptr [rax]
10 and edx,0FFFFFF00h
11 mov rax,qword ptr [rsp]
12 mov ecx,dword ptr [rax]
13 and ecx,0FFFFFF00h
14 xor eax,eax
15 cmp edx,ecx
16 sete al
17 add rsp,18h
18 ret
第2、3、4、5、6、7行似乎是无用的,因为我们可以只使用寄存器rcx和rdx,并且不需要第8行和第11行。第4行和第6行似乎也是无用的,因为没有任何东西使用cmp的结果。我在.net代码中看到了很多这种无用的cmp。
第二个问题是我无法让编译器内联Equals函数。实际上,我很难看到任何东西内联显示。
有什么提示可以让它编译得更好吗?我正在使用Visual Studio 2010和.NET版本4。我正在努力安装4.5和Visual Studio 2013,但可能还需要几天时间。
编辑
所以我尝试了一堆替代方案
这将产生更漂亮的代码,但仍然有点长:
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
public unsafe struct KLF31
{
public UInt16 pos0_1;
public byte pos2;
public bool Equals(ref KLF31 r)
{
return pos0_1 == r.pos0_1 && pos2 == r.pos2;
}
}
return pos0_1 == r.pos0_1 && pos2 == r.pos2;
00000000 mov r8,rdx
00000003 mov rdx,rcx
00000006 movzx ecx,word ptr [rdx]
00000009 movzx eax,word ptr [r8]
0000000d cmp ecx,eax
0000000f jne 0000000000000025
00000011 movzx ecx,byte ptr [rdx+2]
00000015 movzx eax,byte ptr [r8+2]
0000001a xor edx,edx
0000001c cmp ecx,eax
0000001e sete dl
00000021 mov al,dl
00000023 jmp 0000000000000027
00000025 xor eax,eax
00000027 rep ret
这个结构体非常精简,只是大小为4字节而不是3字节。
[StructLayout(LayoutKind.Explicit, Size = 3, Pack = 1)]
public unsafe struct KLF33
{
[FieldOffset(0)] public UInt32 pos0_3;
public bool Equals(ref KLF33 r)
{
return (pos0_3 & 0xFFFFFF00) == (r.pos0_3 & 0xFFFFFF00);
}
}
return (pos0_3 & 0xFFFFFF00) == (r.pos0_3 & 0xFFFFFF00);
00000000 mov rax,rdx
00000003 mov edx,dword ptr [rcx]
00000005 and edx,0FFFFFF00h
0000000b mov ecx,dword ptr [rax]
0000000d and ecx,0FFFFFF00h
00000013 xor eax,eax
00000015 cmp edx,ecx
00000017 sete al
0000001a ret
这个看起来就像那些糟糕的固定字符数组,正如所预期的那样:
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
public unsafe struct KLF34
{
public byte pos0, pos1, pos2;
public bool Equals(ref KLF34 r)
{
fixed (byte* p = &pos0, q = &r.pos0)
{
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
}
}
}
fixed (byte* p = &pos0, q = &r.pos0)
00000000 sub rsp,18h
00000004 mov qword ptr [rsp+8],0
0000000d mov qword ptr [rsp],0
00000015 cmp byte ptr [rcx],0
00000018 mov qword ptr [rsp+8],rcx
0000001d cmp byte ptr [rdx],0
00000020 mov qword ptr [rsp],rdx
{
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
00000024 mov rax,qword ptr [rsp+8]
00000029 mov edx,dword ptr [rax]
0000002b and edx,0FFFFFF00h
00000031 mov rax,qword ptr [rsp]
00000035 mov ecx,dword ptr [rax]
00000037 and ecx,0FFFFFF00h
0000003d xor eax,eax
0000003f cmp edx,ecx
00000041 sete al
00000044 add rsp,18h
00000048 ret
编辑
针对Hans的回复,这里提供样例代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ConsoleApplication2
{
[StructLayout(LayoutKind.Sequential, Size = 3)]
public unsafe struct KLF30
{
public fixed byte Field[3];
public bool Equals(ref KLF30 r)
{
fixed (byte* p = Field, q = r.Field)
{
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
}
}
public bool Equals1(ref KLF30 r)
{
fixed (byte* p = Field, q = r.Field)
{
return p[0] == q[0] && p[1] == q[1] && p[2] == q[2];
}
}
public bool Equals2(ref KLF30 r)
{
fixed (byte* p = Field, q = r.Field)
{
return p[0] == q[0] && p[1] == q[1] && p[2] == q[2];
}
}
}
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
public unsafe struct KLF31
{
public UInt16 pos0_1;
public byte pos2;
public bool Equals(ref KLF31 r)
{
return pos0_1 == r.pos0_1 && pos2 == r.pos2;
}
}
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
public unsafe struct KLF32
{
public fixed byte Field[3];
public bool Equals(ref KLF32 r)
{
fixed (byte* p = Field, q = r.Field)
{
return EqualsImpl(p, q);
}
}
private bool EqualsImpl(byte* p, byte* q)
{
return (*(uint*)p & 0xffffff) == (*(uint*)q & 0xffffff);
}
}
[StructLayout(LayoutKind.Explicit, Size = 3, Pack = 1)]
public unsafe struct KLF33
{
[FieldOffset(0)]
public UInt32 pos0_3;
public bool Equals(ref KLF33 r)
{
return (pos0_3 & 0xFFFFFF00) == (r.pos0_3 & 0xFFFFFF00);
}
}
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
public unsafe struct KLF34
{
public byte pos0, pos1, pos2;
public bool Equals(ref KLF34 r)
{
fixed (byte* p = &pos0, q = &r.pos0)
{
return ((*(UInt32*)p) & 0xFFFFFF00) == ((*(UInt32*)q) & 0xFFFFFF00);
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Klf
{
[FieldOffset(0)] public char pos0;
[FieldOffset(1)] public char pos1;
[FieldOffset(2)] public char pos2;
[FieldOffset(3)] public char pos3;
[FieldOffset(4)] public char pos4;
[FieldOffset(5)] public char pos5;
[FieldOffset(6)] public char pos6;
[FieldOffset(7)] public char pos7;
[FieldOffset(0)] public UInt16 pos0_1;
[FieldOffset(2)] public UInt16 pos2_3;
[FieldOffset(4)] public UInt16 pos4_5;
[FieldOffset(6)] public UInt16 pos6_7;
[FieldOffset(0)] public UInt32 pos0_3;
[FieldOffset(4)] public UInt32 pos4_7;
[FieldOffset(0)] public UInt64 pos0_7;
}
[StructLayout(LayoutKind.Sequential, Size = 3)]
public unsafe struct KLF35
{
public Klf Field;
public bool Equals(ref KLF35 r)
{
return (Field.pos0_3 & 0xFFFFFF00) == (r.Field.pos0_3 & 0xFFFFFF00);
}
}
public unsafe class KlrAAFI
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct _AAFI
{
public KLF30 AirlineCxrCode0;
public KLF31 AirlineCxrCode1;
public KLF32 AirlineCxrCode2;
public KLF33 AirlineCxrCode3;
public KLF34 AirlineCxrCode4;
public KLF35 AirlineCxrCode5;
}
public KlrAAFI(byte* pData)
{
Data = (_AAFI*)pData;
}
public _AAFI* Data;
public int Size = sizeof(_AAFI);
}
class Program
{
static unsafe void Main(string[] args)
{
byte* foo = stackalloc byte[256];
var a1 = new KlrAAFI(foo);
var a2 = new KlrAAFI(foo);
var p1 = a1.Data;
var p2 = a2.Data;
//bool f01= p1->AirlineCxrCode0.Equals (ref p2->AirlineCxrCode0);
//bool f02= p1->AirlineCxrCode0.Equals1(ref p2->AirlineCxrCode0);
//bool f03= p1->AirlineCxrCode0.Equals2(ref p2->AirlineCxrCode0);
//bool f1 = p1->AirlineCxrCode1.Equals (ref p2->AirlineCxrCode1);
bool f2 = p1->AirlineCxrCode2.Equals (ref p2->AirlineCxrCode2);
//bool f3 = p1->AirlineCxrCode3.Equals (ref p2->AirlineCxrCode3);
//bool f4 = p1->AirlineCxrCode4.Equals (ref p2->AirlineCxrCode4);
//bool f5 = p1->AirlineCxrCode5.Equals (ref p2->AirlineCxrCode5);
//int q = f01 | f02 | f03 | f1 | f2 | f3 | f4 ? 0 : 1;
int q = f2 ? 0 : 1;
Console.WriteLine("{0} {1} {2} {3} {4} {5}",
sizeof(KLF30), sizeof(KLF31), sizeof(KLF32), sizeof(KLF33), sizeof(KLF34), sizeof(KLF35));
Console.WriteLine("{0}", q);
}
}
}
当我将除f2外的所有内容注释掉并编译时,我得到了这个结果:
var p1 = a1.Data;
0000007b mov rax,qword ptr [rdi+8]
var p2 = a2.Data;
0000007f mov rcx,qword ptr [rbx+8]
bool f2 = p1->AirlineCxrCode2.Equals (ref p2->AirlineCxrCode2);
00000083 cmp byte ptr [rax],0
00000086 add rax,10h
0000008c cmp byte ptr [rcx],0
0000008f add rcx,10h
00000093 xor edx,edx
00000095 mov qword ptr [rbp],rdx
00000099 mov qword ptr [rbp+8],rdx
0000009d cmp byte ptr [rax],0
000000a0 mov qword ptr [rbp],rax
000000a4 cmp byte ptr [rcx],0
000000a7 mov qword ptr [rbp+8],rcx
000000ab mov rax,qword ptr [rbp]
000000af mov rcx,qword ptr [rbp+8]
000000b3 mov edx,dword ptr [rax]
000000b5 and edx,0FFFFFFh
000000bb mov ecx,dword ptr [rcx]
000000bd and ecx,0FFFFFFh
000000c3 xor eax,eax
000000c5 cmp edx,ecx
000000c7 sete al
000000ca movzx ecx,al
000000cd movzx eax,cl
如果您仔细查看汇编代码,正如Hans所指出的那样,它被内联了,但是大部分的汇编代码并没有实现任何功能。请看000000c5之前所有无用的cmp语句。请看它将同一个值多次移动到和从rbp和rbp+8中的操作。也许我不理解它的实用性。
如果您注释掉除f1以外的所有内容,我会得到这个结果:
var p1 = a1.Data;
00000071 mov rdx,qword ptr [rdi+8]
var p2 = a2.Data;
00000075 mov r8,qword ptr [rbx+8]
bool f1 = p1->AirlineCxrCode1.Equals (ref p2->AirlineCxrCode1);
00000079 cmp byte ptr [rdx],0
0000007c cmp byte ptr [r8],0
00000080 movzx ecx,word ptr [rdx+8]
00000084 movzx eax,word ptr [r8+8]
00000089 cmp ecx,eax
0000008b jne 00000000000000A2
0000008d movzx ecx,byte ptr [rdx+0Ah]
00000091 movzx eax,byte ptr [r8+0Ah]
00000096 xor edx,edx
00000098 cmp ecx,eax
0000009a sete dl
0000009d movzx eax,dl
000000a0 jmp 00000000000000A4
000000a2 xor eax,eax
这个版本仍然有无用的cmp指令79、7c,但开销要少得多。
在这种情况下,fixed似乎会生成大量(无用的?)汇编代码。