【问题标题】:Faster way to convert byte array to int将字节数组转换为 int 的更快方法
【发布时间】:2011-05-18 14:07:25
【问题描述】:

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

【问题讨论】:

  • 当然,只要问谷歌 :D 你使用什么编程语言? BitConverter 似乎是.Net。我的猜测是读取字节数组可能需要更多时间然后转换它,只需在该问题中添加一些额外信息即可。
  • 为什么?是性能瓶颈吗?
  • 对不起,我的解释不完整。我用的是c#,我用BitConverter.ToInt32将一个字节数组转换为int,它几乎用了200ms,有什么更快的方法吗?
  • 秒表 sw = new Stopwatch(); sw.Start();字典 sd = new Dictionary(); Console.WriteLine(sw.GetSplitTimeInMicroseconds()); int unitid = BitConverter.ToInt32(units, 7); // 单位是一个字节数组 Console.WriteLine(sw.GetSplitTimeInMicroseconds()); // 输出 113.220279110632 383.590305626821
  • @jacky - 超过 1 次迭代的计时并不是一个好的衡量标准。做 500 万次,然后除以 5M

标签: .net bytearray converter


【解决方案1】:

我实际上尝试了几种不同的方法来将四个字节转换为 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);

我在 2.5 GHz Core i7 笔记本电脑的调试器下,在 Release (x86) 版本中运行了每个版本的 10^9 次迭代。这是我的结果(请注意,不使用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 表明,在我的笔记本电脑上,很难让转换速度低于 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);
        }
    }
}

【讨论】:

  • 有趣。但这对总程序时间有多重要? (即,从微优化与持续维护成本中获益有限)。我在 32 位上得到了类似的数据,但如果禁用 1 和 2 则从 3 到 6 看到更快的结果(因此 GC 不那么忙——根据 gen 0 收集计数,1 和 2 的很大一部分时间)。
  • 顺便说一句。为 64 位方式 5 和 6 运行 AnyCPU 速度更快。
  • 进一步思考......这些测试将真正受益于缓存局部性。在计时之前构建一个随机的byte[],足够大,以至于计时循环是少量的通过,它可能会通过炸毁数据缓存来显示更真实的价值(这将导致测试 5 和 6 丢失大部分他们的优势)。
  • 在进一步的测试中,测试 5 和 6 正在得到优化:保持转换的运行总数(long 足够大)并且时间显着增加。这会从代码中的循环中删除常用表达式提取。
  • @Richard:我想过 CSE,但如果是这样,为什么 5 和 6 的时间不同?为什么它们的运行速度都是空循环的两倍多?
【解决方案2】:

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

然而,从该领域的大量工作来看,这不太可能成为真正的瓶颈,以至于无关紧要。 I/O 通常是主要问题。

GetBytes(int),但是,由于数组/堆分配,更昂贵(大容量)。

【讨论】:

  • iPod,认真的吗?您可以在 iPod 上做比这方式更好的事情。另外,不支持 标签?那是怎么回事?
【解决方案3】:

跟进Gabe's 性能测试:

变化:

  • 消除测试 1 和 2,因为内联数组的创建对 GC 进行了这些测试(从 Gen 0 GC 性能计数器可以看出)。
  • 消除测试 4(非对齐数组)以简化操作。
  • 添加测试 7 和 8,它们分别通过 BitConverter 和位摆弄从大型数组 (256 MB) 进行转换。
  • 将运行总计添加到测试中,以避免常见的子表达式消除,这显然会导致 Gabe 的测试 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. 在 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);
        }
    }
}

【讨论】:

  • 您赢得了 64 位的胜利,因为您使用的是 long 累加器。
  • 看起来即使有循环、内存访问和累积的所有额外开销,调用ToInt32 的速度仍然是内联编写计算的两倍。
【解决方案4】:

基于对 .NET Reflector 中 BitConverter.ToInt32 实现的快速回顾,我会说“”。

它针对数组对齐并直接转换字节的情况进行优化,否则它执行按位合并。

【讨论】:

  • 由于函数调用开销,内联编写代码很可能会更快。
  • @Gabe:JIT 会进行内联,所以如果没有基准,我不会打赌——然后还有 Marc 的 Q re。这种方法的性能在功能层面的重要性。
  • 这是我的基准测试:stackoverflow.com/questions/4326125/… 随意找出我做错了什么。
【解决方案5】:

我也遇到过类似的问题。

在我的情况下,当数据存储为双精度 byte[]s 或仅在 double 表示和 byte[] 表示等之间时,如何转换为单精度 floats。最好的不是如果想要在大型数据集上获得最佳性能,则需要通过过多的 API 层,并在不使其过于脆弱或难以理解的情况下将尽可能多的信息嵌入到算法中。

因此,为了进一步跟进 Richard's 测试,我在下面添加了另一个测试 (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);
        }

    }
}

【讨论】:

    【解决方案6】:

    我总结了以上所有内容,添加了Span 变体并使用了基准框架。

    public class ByteArrayToIntBench
    {
        private readonly byte[] _array = new byte[4 * 10_000];
    
        public ByteArrayToIntBench()
        {
            Random r = new Random();
            for (int i = 0; i < _array.Length; i++)
            {
                _array[i] = (byte)r.Next(byte.MinValue, byte.MaxValue);
            }
        }
    
        [Benchmark]
        public double Bitconverter()
        {
            double res = 0;
            for (int i = 0; i < _array.Length; i += 4)
            {
                res += BitConverter.ToInt32(_array, i);
            }
            return res;
        }
    
        [Benchmark]
        public unsafe double Unsafe()
        {
            double res = 0;
            for (int i = 0; i < _array.Length; i += 4)
            {
                fixed (byte* pData = &_array[i])
                {
                    res += *(int*)pData;
                }
            }
            return res;
        }
    
        [Benchmark]
        public double Shift()
        {
            double res = 0;
            for (int i = 0; i < _array.Length; i += 4)
            {
                res += _array[i] | (_array[i + 1] << 8) | (_array[i + 2] << 16) | (_array[i + 3] << 24);
            }
            return res;
        }
    
        [Benchmark]
        public double Span()
        {
            double res = 0;
            for (int i = 0; i < _array.Length; i += 4)
            {
                res += MemoryMarshal.Cast<byte, int>(_array.AsSpan(i, 4))[0];
            }
            return res;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-04
      • 2020-01-20
      • 2013-06-25
      • 2017-11-30
      • 2010-10-22
      相关资源
      最近更新 更多