C#比较3个字节字段

12

编辑

未使用的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似乎会生成大量(无用的?)汇编代码。


2
在 .Net 4.5 中,您可以在 MethodImpl 属性中指定 MethodImplOptions.AggressiveInlining - Blorgbeard
此外,这个问题可能会引起兴趣。链接 - Blorgbeard
@Blorg 我正在尝试安装它。但我不知道这是否能解决所有问题。 - johnnycrash
@Blorgbeard。我已经关闭了“抑制优化”一段时间了。但昨天这个选项让我感到困惑! - johnnycrash
我觉得我还缺少一些东西。那是CLI,而在运行时将被转换和优化为本机汇编代码。我不确定做这种深入分析的好处是什么。 - Wagner DosAnjos
2
@wdosanjos 不是,那是x86汇编语言。它已经从CIL JIT编译过来了。 - Blorgbeard
1个回答

8

是的,优化器在这段代码上不太适应,它对锁定操作不太满意。你可以通过编写单独的方法来解决:

    public bool Equals(ref KLF3 r) {
        fixed (byte* p = Field, q = r.Field) {
            return EqualsImpl(p, q);
        }
    }
    private unsafe bool EqualsImpl(byte* p, byte* q) {
        return (*(uint*)p & 0xffffff) == (*(uint*)q & 0xffffff);
    }

这使得它更加智能化:
0000006b  mov         rax,qword ptr [rsp+20h] 
00000070  mov         rcx,qword ptr [rsp+28h] 
00000075  mov         edx,dword ptr [rax] 
00000077  and         edx,0FFFFFFh 
0000007d  mov         ecx,dword ptr [rcx] 
0000007f  and         ecx,0FFFFFFh 
00000085  xor         eax,eax 
00000087  cmp         edx,ecx 
00000089  sete        al 
0000008c  movzx       ecx,al 
0000008f  movzx       ecx,cl 

在调用方法中生成内联内容。此外,非常重要的是要对不传递引用参数的版本进行分析,应该会更快,而您当前的版本会导致太多的意外情况。我更改了您的位屏蔽码,在小端机器上应该为0xffffff。


我差点以为你做到了,但是我看到在外部Equals()的汇编中有相同的额外asm:fixed (byte* p = Field, q = r.Field) 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 - johnnycrash
不,它被内联了。当然,我不能保证在你的情况下也会这样,因为你没有发布完整的示例。 - Hans Passant
1
不,Equals和EqualsImpl都被内联了。祝你好运。 - Hans Passant
检查我的编辑。我添加了示例代码。它并没有完全做到我的其他测试程序所做的,但它显示了很多额外的汇编代码,你好像没有。我真的要在4.5中测试这个。 - johnnycrash
@Scott 我编译了一个 64 位的发布应用,并且关闭了“抑制优化”调试设置。接着我运行应用,在 main 函数中设置断点,查看生成的代码。通常在所有函数被调用的部分后面停止。 - johnnycrash
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接