Быстрое литье в С# с использованием BitConverter, может ли оно быть быстрее?

В нашем приложении у нас очень большой байтовый массив, и мы должны преобразовать эти байты в разные типы. В настоящее время для этой цели мы используем BitConverter.ToXXXX(). Наши тяжелые нападающие: ToInt16 и ToUInt64.

Для UInt64 наша проблема заключается в том, что поток данных имеет фактически 6 байтов данных для представления большого целого. Поскольку нет встроенной функции для преобразования 6-байтов данных в UInt64, мы делаем:

UInt64 value = BitConverter.ToUInt64() & 0x0000ffffffffffff;

Наше использование ToInt16 проще, не нужно делать никаких манипуляций с битами.

Мы делаем так много из этих двух операций, что я хотел спросить сообщество SO, есть ли более быстрый способ сделать эти преобразования. В настоящее время около 20% всех циклов процессора потребляются этими двумя функциями.

Ответы

Ответ 1

Вы думали об использовании указателей памяти напрямую. Я не могу ручаться за его производительность, но это общий трюк в С++\C...

        byte[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9,10,11,12,13,14,15,16};

        fixed (byte* a2rr = &arr[0])
        {

            UInt64* uint64ptr = (UInt64*) a2rr;
            Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF));
            uint64ptr = (UInt64*) ((byte*) uint64ptr+6);
            Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF));
        }

Вам нужно будет сделать сборку "небезопасной" в настройках сборки, а также пометьте метод, в котором вы будете делать это небезопасно. С этим подходом вы также привязаны к маленькому концу.

Ответ 2

Вы можете использовать класс System.Buffer, чтобы скопировать весь массив в другой массив другого типа как быстрый, 'block copy':

Метод BlockCopy обращается к байтам в массиве параметров src, используя смещения в памяти, а не в конструкциях программирования, таких как индексы или верхние и нижние границы массива.

Типы массивов должны быть типа "примитивные", они должны быть выровнены, а операция копирования - с учетом полномочий. В вашем случае с целыми числами 6 байтов он не может быть выровнен с любым из примитивных типов .NET, если вы не можете получить исходный массив с двумя байтами заполнения для каждого шести, который затем будет привязан к Int64. Но этот метод будет работать для массивов Int16, что может ускорить некоторые из ваших операций.

Ответ 3

Почему бы и нет:

UInt16 valLow = BitConverter.ToUInt16();
UInt64 valHigh = (UInt64)BitConverter.ToUInt32();
UInt64 Value = (valHigh << 16) | valLow;

Вы можете сделать это один оператор, хотя компилятор JIT, вероятно, сделает это за вас автоматически.

Это не даст вам прочитать эти лишние два байта, которые вы в итоге выбросите.

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

Обратите внимание, что, как отметил комментатор, если вы используете какие-либо из этих предложений, то либо вы ограничены определенной "endian-ness", либо вам придется писать свой код, чтобы обнаружить little/big endian и реагировать соответственно. Образец кода, показанный выше, работает для небольшого конца (x86).

Ответ 4

См. мой ответ на аналогичный вопрос здесь. Это та же самая небезопасная манипуляция с памятью, как в ответ Джимми, но более "дружеский" способ для потребителей. Это позволит вам просматривать массив byte как массив UInt64.