С# сравнить 3 байтовое поле

ИЗМЕНИТЬ

Команды cmp, которые не используются, вызывают исключение NullPointerException.

Что представляют собой эти странные инструкции cmp [ecx], ecx в моем коде С#?

ОРИГИНАЛЬНАЯ ПОЧТА (еще больше изменений ниже)

Я пытаюсь понять способ компиляции кода JIT.

В памяти у меня есть поле 3 char. В С++ для сравнения двух таких полей я могу это сделать:

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 

В С# я пытаюсь понять, как можно приблизиться к этому, насколько я могу. У нас есть записи, состоящие из большого количества 1,2,3,4,5,6,7,8 байтовых полей. Я проверил множество разных способов в С#, чтобы построить большую структуру, представляющую запись, используя меньшие структуры этих размеров. Я не удовлетворен кодом сборки. Прямо сейчас я играю с чем-то вроде этого:

[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. Я вижу много этих бесполезных cmps в .net-коде.

Проблема 2: я не могу заставить компилятор встроить функцию Equals. На самом деле мне нелегко видеть, что что-то идет внутрь.

Любые подсказки, чтобы получить это, чтобы скомпилировать лучше? Я использую Visual Studio 2010 и .net версии 4. Я работаю, чтобы получить 4.5 установленную и визуальную студию 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 

Этот выглядит точно так же, как и жуткий фиксированный массив char, как и ожидалось:

[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 

ИЗМЕНИТЬ

В ответ на Ганса, вот пример кода.

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 

Если вы внимательно посмотрите на сборку, она будет указана, как указал Ханс, но большая часть этого asm ничего не делает. Посмотрите на все бесполезные выражения cmp до 000000c5. Посмотрите, сколько раз он перемещает одно и то же значение в и из 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 instr 79, 7c, но намного меньше накладных расходов.

Кажется, что фиксированное генерирует много (бесполезных?) asm в этом случае.

Ответ 1

Да, оптимизатор ошеломляет этот код, он не очень доволен пиннингами. Вы можете ударить его по голове, написав отдельный метод:

    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 

Сгенерировано inline в методе вызывающего абонента. Также очень важно, чтобы вы просматривали версию, которая не передает аргумент ref, должна быть быстрее, и ваша текущая версия вызывает слишком много аварий. Я изменил свои битмаски, они должны быть 0xffffff на маленькой машине.