将字节数组转换为int的更快方法

jac*_*cky 3 .net bytearray converter

有没有比BitConverter.ToInt32将字节数组转换为int值更快的方法?

Gab*_*abe 19

我实际上尝试了几种不同的方法将四个字节转换为int:

  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);

我在Release(x86)版本中运行了10 ^ 9次迭代,而不是在2.5 GHz 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
Run Code Online (Sandbox Code Playgroud)

您可以得出一些结论:

  • test1显示在我的笔记本电脑上很难让转换速度低于15ns,我不愿意说这对任何人都应该足够快.(你需要每秒调用超过60M次?)
  • test2表明使用uint而不是int节省少量时间.我不确定为什么,但我认为它足够小,可以成为实验性错误.
  • test3表明创建一个新的字节数组(7ns)的开销几乎与调用该函数一样多,但仍然比从旧数组中创建一个新数组要快.
  • test4显示从未ToInt32添加的数组访问增加开销(3ns)
  • test5显示从局部变量中提取4个字节并自己组合它们比调用快几倍ToInt32.
  • test6表明,从数组中提取4个字节实际上比从函数参数提取的速度稍快一些!我怀疑这是由于CPU流水线或缓存效应.

最快的test6只需要两倍的空循环运行(未显示).换句话说,执行每次转换所需的时间不到1ns.祝你好运,获得任何有用的计算都要快得多!

这是我的测试程序:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 6

如果我没记错的话,那个实现使用了不安全的代码(将一个字节*视为一个int*),所以它很难被击败,但另一种选择是转移.

但是,从这个领域的大量工作来看,这不太可能成为一个真正的瓶颈而不相关.通常,I/O是主要问题.

GetBytes会(INT),然而,更昂贵的(在高容量)由于阵列/堆分配.


Ric*_*ard 5

Gabe性能测试的后续:

变化:

  • 消除测试 1 和 2,因为内联数组的创建对 GC 进行了这些测试(从 Gen 0 GC 性能计数器可以看出)。
  • 消除测试 4(非对齐数组)以使事情更简单。
  • 添加测试 7 和 8,它们分别通过 BitConverter 和 bit fddling 从大数组 (256 MB) 进行转换。
  • 将运行总计添加到测试中以尝试避免常见的子表达式消除,这显然导致 Gabe 的测试 5 和 6 中的低时间。

结果:

分析

  1. 仍然在 64 位上消除 5 和 6 中的常见子表达式。
  2. 对于这个 64 位是一个胜利。但是,在选择优化应用程序的位置时,不应遵循这样的微基准测试。
  3. 将 256 MB 的随机数据转换为整数时,看起来大约有 50% 的改进。由于测试进行了 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)