Более быстрый способ преобразования байтового массива в int - PullRequest
3 голосов
/ 01 декабря 2010

Есть ли более быстрый способ, чем BitConverter.ToInt32 для преобразования байтового массива в значение типа int?

Ответы [ 5 ]

18 голосов
/ 02 декабря 2010

Я на самом деле пробовал несколько разных способов преобразовать четыре байта в целое:

  1. BitConverter.ToInt32(new byte[] { w, x, y, z }, 0);
  2. BitConverter.ToUInt32(new byte[] { w, x, y, z }, 0);
  3. b = new byte[] { w, x, y, z }; BitConverter.ToInt32(b, 0);
  4. b = new byte[] { 1, 2, 3, 4, 5, 6, 7, w, x, y, z }; BitConverter.ToInt32(b, 7);
  5. w | (x << 8) | (y << 16) | (z << 24);
  6. b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);

Я выполнил 10 ^ 9 итераций каждой из них в сборке выпуска (x86), а не под отладчиком на ноутбуке с тактовой частотой 2,5 ГГц Core i7 . Вот мои результаты (обратите внимание, что методы, которые не используют BitConverter, существенно быстрее):

test1: 00:00:15.5287282 67305985
test2: 00:00:15.1334457 67305985
test3: 00:00:08.0648586 67305985
test4: 00:00:11.2307059 67305985
test5: 00:00:02.0219417 67305985
test6: 00:00:01.6275684 67305985

Некоторые выводы, которые вы можете сделать:

  • test1 показывает, что на моем ноутбуке преобразование происходит медленнее, чем 15 нс, что, как я ненавижу, должно быть достаточно быстрым для всех. (Вам нужно вызывать его более 60 миллионов раз в секунду?)
  • test2 показывает, что использование uint вместо int экономит небольшое количество времени. Я не уверен, почему, но я думаю, что он достаточно мал, чтобы быть экспериментальной ошибкой.
  • test3 показывает, что накладные расходы на создание нового байтового массива (7 нс) почти равны вызову функции, но все же быстрее, чем создание нового массива из старого массива.
  • test4 показывает, что при доступе к невыровненному массиву из ToInt32 добавляются издержки (3 нс)
  • test5 показывает, что вытащить 4 байта из локальных переменных и объединить их самостоятельно в несколько раз быстрее, чем вызвать ToInt32.
  • test6 показывает, что на самом деле немного быстрее извлечь 4 байта из массива, чем из аргументов функции! Я подозреваю, что это связано с конвейерной обработкой процессора или эффектами кэша.

Самый быстрый, test6, выполнялся всего в два раза дольше, чем пустой цикл (не показан). Другими словами, для каждого преобразования потребовалось менее 1 нс. Удачи в получении любого полезного вычисления, чтобы идти быстрее этого!

Вот моя тестовая программа:

using System;

namespace BitConverterTest
{
    class Program
    {
        const int iters = 1000000000;
        static void Main(string[] args)
        {
            test1(1, 2, 3, 4);
            test2(1, 2, 3, 4);
            test3(1, 2, 3, 4);
            test4(1, 2, 3, 4);
            test5(1, 2, 3, 4);
            test6(1, 2, 3, 4);
        }

        static void test1(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToInt32(new byte[] { w, x, y, z }, 0);
            Console.WriteLine("test1: " + timer.Elapsed + " " + res);
        }

        static void test2(byte w, byte x, byte y, byte z)
        {
            uint res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToUInt32(new byte[] { w, x, y, z }, 0);
            Console.WriteLine("test2: " + timer.Elapsed + " " + res);
        }

        static void test3(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToInt32(b, 0);
            Console.WriteLine("test3: " + timer.Elapsed + " " + res);
        }

        static void test4(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { 1, 2, 3, 4, 5, 6, 7, w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToInt32(b, 7);
            Console.WriteLine("test4: " + timer.Elapsed + " " + res);
        }

        static void test5(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = w | (x << 8) | (y << 16) | (z << 24);
            Console.WriteLine("test5: " + timer.Elapsed + " " + res);
        }

        static void test6(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
            Console.WriteLine("test6: " + timer.Elapsed + " " + res);
        }
    }
}
6 голосов
/ 01 декабря 2010

Если я правильно помню, эта реализация использует небезопасный код (обрабатывая байт * как целое число *), поэтому его будет трудно обойти, но другой вариант сдвигается.

Однако из лотов работ в этой области это вряд ли будет подлинным узким местом, так как не будет иметь значения. Как правило, основной проблемой является ввод / вывод.

GetBytes (int), однако, стоит дороже (в большом объеме) из-за выделения массива / кучи.

4 голосов
/ 02 декабря 2010

Последующие за Тесты производительности Габе:

Изменения:

  • Устранить тесты 1 и 2, потому что создание встроенного массива сделало эти тесты GC (как видно из счетчика производительности GC Gen 0).
  • Отменить тест 4 (невыровненный массив), чтобы упростить задачу.
  • Добавить тесты 7 и 8, которые делают преобразования из большогомассив (256 МБ) через BitConverter и битовую переписку соответственно.
  • Добавьте промежуточные итоги к тестам, чтобы попытаться избежать исключения общих подвыражений, что явно приводит к малым временам в тестах Гейба 5 и 6.

Результаты:

  • 32-битная опция:

    test3: 00:00:06.9230577
    test5: 00:00:03.8349386
    test6: 00:00:03.8238272
    test7: 00:00:07.3898489
    test8: 00:00:04.6807391
    
  • 64-битная опция:

    test3: 00:00:05.8794322
    test5: 00:00:00.4384600
    test6: 00:00:00.4069573
    test7: 00:00:06.2279365
    test8: 00:00:03.5472486
    

Анализ

  1. По-прежнему получаются обычные исключения подвыражений в 5 и 6 на 64-битных.
  2. Для этого 64-битного это выигрыш.Но такой микро-эталон не должен соблюдаться при выборе места для оптимизации приложения.
  3. Похоже, улучшение примерно на 50% при преобразовании 256 МБ случайных данных в целые.Так как тест проделывает это 16 раз, это меньше 0,2 с - вряд ли это реально изменится за пределами очень узкого подмножества приложений, и тогда у вас есть дополнительные расходы на обслуживание, гарантирующие, что кто-то не нарушит код в течение срока службы приложения..
  4. Интересно, сколько из BitConverter служебных данных выполняет проверка параметров?
  5. Тест 6 только немного быстрее, чем 5. Очевидно, что проверки границ массива устраняются.

Код

using System;

namespace BitConverterTest {
    class Program {
        const int iters = 1024*1024*1024;
        const int arrayLen = iters/4;
        static byte[] array = new byte[arrayLen];

        static void Main(string[] args) {
            //test1(1, 2, 3, 4);
            //test2(1, 2, 3, 4);
            test3(1, 2, 3, 4);
            //test4(1, 2, 3, 4);
            test5(1, 2, 3, 4);
            test6(1, 2, 3, 4);

            // Fill array with good PRNG data
            var rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
            rng.GetBytes(array);

            test7();
            test8();
        }

        // BitConverter with aligned input
        static void test3(byte w, byte x, byte y, byte z) {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToInt32(b, 0);
            Console.WriteLine("test3: " + timer.Elapsed + " " + res);
        }

        // Inline bitfiddling with separate variables.
        static void test5(byte w, byte x, byte y, byte z) {
            long res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++) {
                int a = w | (x << 8) | (y << 16) | (z << 24);
                res += a;
            }
            Console.WriteLine("test5: " + timer.Elapsed + " " + res);
        }

        // Inline bitfiddling with array elements.
        static void test6(byte w, byte x, byte y, byte z) {
            long res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++) {
                int a = b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
                res += a;
            }
            Console.WriteLine("test6: " + timer.Elapsed + " " + res);
        }

        // BitConvert from large array...
        static void test7() {
            var its = iters/arrayLen * 4; // *4 to remove arrayLen/4 factor.
            var timer = System.Diagnostics.Stopwatch.StartNew();
            long res = 0;
            for (var outer = 0; outer < its; outer++) {
                for (var pos = 0; pos < arrayLen; pos += 4) {
                    var x = BitConverter.ToInt32(array, pos);
                    res += x;
                }
            }
            Console.WriteLine("test7: " + timer.Elapsed + " " + res);
        }

        // Bitfiddle from large array...
        static void test8() {
            var its = iters/arrayLen * 4;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            long res = 0;
            for (var outer = 0; outer < its; outer++) {
                for (var pos = 0; pos < arrayLen; pos += 4) {
                    int x = array[pos] | (array[pos+1] << 8) | (array[pos+2] << 16) | (array[pos+3] << 24);
                    res += x;
                }
            }
            Console.WriteLine("test8: " + timer.Elapsed + " " + res);
        }
    }
}
3 голосов
/ 01 декабря 2010

Основываясь на кратком обзоре реализации BitConverter.ToInt32 в .NET Reflector, я бы сказал: " Нет ".

Оптимизируется для случая, когдамассив выравнивается и напрямую преобразует байты, в противном случае он выполняет побитовое слияние.

0 голосов
/ 10 ноября 2016

Я также возился с подобными проблемами.

В моем случае это был способ преобразования в единичную точность float с, когда данные хранятся в формате byte[] с двойной точностью, или просто между представлением double и представлением byte[] и т. Д. Лучше нет пройти через слишком много уровней API, если вы хотите добиться максимальной производительности на больших наборах данных, и вложить как можно больше информации в алгоритм, не делая его слишком хрупким или непонятным.

Итак, для дальнейшего продолжения тестов Ричарда, я добавляю еще один тест ниже (test9), который является способом, которым я прошел в своей собственной работе, и отвечаю на его пункт 4 в разделе «Анализ» :

Использование небезопасного доступа к указателю памяти для достижения наиболее производительного результата. Что-то, что естественно, если вы используете c ++, но не обязательно c #. Это похоже на то, что BitConverter делает под капотом, но без проверки параметров и безопасности (как, конечно, мы знаем, что делаем ...;)

Результаты:

  • 32-битная опция:

    test3: 00:00:06.2373138
    test5: 00:00:03.1193338
    test6: 00:00:03.1609287
    test7: 00:00:07.7328020
    test8: 00:00:06.4192130
    test9: 00:00:03.9590307
    
  • 64-битная опция:

    test3: 00:00:06.2209098
    test5: 00:00:00.5563930
    test6: 00:00:01.5486780
    test7: 00:00:08.4858474
    test8: 00:00:05.4991740
    test9: 00:00:02.2928944
    

Здесь тот же код, включая новый test9:

using System;

namespace BitConverterTest
{
    class Program
    {
        const int iters = 1024 * 1024 * 1024;
        const int arrayLen = iters / 4;
        static byte[] array = new byte[arrayLen];

        static void Main(string[] args)
        {
            //test1(1, 2, 3, 4);
            //test2(1, 2, 3, 4);
            test3(1, 2, 3, 4);
            //test4(1, 2, 3, 4);
            test5(1, 2, 3, 4);
            test6(1, 2, 3, 4);

            // Fill array with good PRNG data
            var rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
            rng.GetBytes(array);

            test7();
            test8();
            test9();
        }

        // BitConverter with aligned input
        static void test3(byte w, byte x, byte y, byte z)
        {
            int res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
                res = BitConverter.ToInt32(b, 0);
            Console.WriteLine("test3: " + timer.Elapsed + " " + res);
        }

        // Inline bitfiddling with separate variables.
        static void test5(byte w, byte x, byte y, byte z)
        {
            long res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
            {
                int a = w | (x << 8) | (y << 16) | (z << 24);
                res += a;
            }
            Console.WriteLine("test5: " + timer.Elapsed + " " + res);
        }

        // Inline bitfiddling with array elements.
        static void test6(byte w, byte x, byte y, byte z)
        {
            long res = 0;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            var b = new byte[] { w, x, y, z };
            for (int i = 0; i < iters; i++)
            {
                int a = b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
                res += a;
            }
            Console.WriteLine("test6: " + timer.Elapsed + " " + res);
        }

        // BitConvert from large array...
        static void test7()
        {
            var its = iters / arrayLen * 4; // *4 to remove arrayLen/4 factor.
            var timer = System.Diagnostics.Stopwatch.StartNew();
            long res = 0;
            for (var outer = 0; outer < its; outer++)
            {
                for (var pos = 0; pos < arrayLen; pos += 4)
                {
                    var x = BitConverter.ToInt32(array, pos);
                    res += x;
                }
            }
            Console.WriteLine("test7: " + timer.Elapsed + " " + res);
        }

        // Bitfiddle from large array...
        static void test8()
        {
            var its = iters / arrayLen * 4;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            long res = 0;
            for (var outer = 0; outer < its; outer++)
            {
                for (var pos = 0; pos < arrayLen; pos += 4)
                {
                    int x = array[pos] | (array[pos + 1] << 8) | (array[pos + 2] << 16) | (array[pos + 3] << 24);
                    res += x;
                }
            }
            Console.WriteLine("test8: " + timer.Elapsed + " " + res);
        }

        // unsafe memory operations from large array...
        // (essentialy internals of BitConverter without param checks, etc)
        static unsafe void test9()
        {
            var its = iters / arrayLen * 4;
            var timer = System.Diagnostics.Stopwatch.StartNew();
            long res = 0;
            int value = 0;
            for (var outer = 0; outer < its; outer++)
            {
                for (var pos = 0; pos < arrayLen; pos += 4)
                {
                    fixed (byte* numPtr = &array[pos])
                    {
                        value = *(int*)numPtr;
                    }
                    int x = *(int*)&value;
                    res += x;
                }
            }
            Console.WriteLine("test9: " + timer.Elapsed + " " + res);
        }

    }
}
...