【问题标题】:Fastest way to convert int to 4 bytes in C#在 C# 中将 int 转换为 4 个字节的最快方法
【发布时间】:2012-02-08 07:16:58
【问题描述】:

在 C# 中将 int 转换为 4 字节的最快方法是什么?

最快的是执行时间而不是开发时间。

我自己的解决方案是这段代码:

byte[] bytes = new byte[4];
unchecked
{
 bytes[0] = (byte)(data >> 24);
 bytes[1] = (byte)(data >> 16);
 bytes[2] = (byte)(data >> 8);
 bytes[3] = (byte)(data);
}

现在我发现我的解决方案比 structBitConverter 都好几个滴答声。

我认为 unsafe 可能是最快的选项并接受它作为答案,但我更愿意使用托管选项。

【问题讨论】:

  • fastest 是指 a) 最少的代码量还是 b) 最好的性能?
  • “最快”取决于您希望这些字节的位置。
  • 你有多少内存?如果整数都是 +ve 并且值的范围不大,那么查找数组怎么样? (不确定这会比一个位移更快)。
  • 它不适用于我的情况,但它是原创的:)。
  • 供参考,如C# Language Specification, Shift Operators 中所述:移位操作永远不会导致溢出,并且在检查和未检查的上下文中产生相同的结果。所以问题中未经检查的陈述是矫枉过正的。

标签: c# .net bit-manipulation


【解决方案1】:

在 C# 中将 int 转换为 4 字节的最快方法是什么?

使用BitConverter,它是GetBytes 重载,采用32 位整数:

int i = 123;
byte[] buffer = BitConverter.GetBytes(i);

【讨论】:

  • @TomTom:那么不安全会更快吗?
  • @TomTom,有趣的解决方案。但如果速度相似,我更喜欢 BCL 方法调用,它也更短且不易出错。您不需要将程序集标记为不安全,因为 BitConverter.GetBytes 是 BCL 方法。我同意,这是不安全的。我们一直在使用的 BCL 中的大量方法都是不安全的。
  • @TomTom,好吧,我不打算和你争论。你不会让我改变主意,我也不会让你改变主意。所以我不想继续这个讨论,因为它对我来说毫无意义。我已经公开了我的解决方案,您已经公开了您的解决方案,我尊重您的解决方案。这就是 Stack Overflow 是个好地方的原因。因为我们可以看到解决问题的不同方法。我认为社区只能从这种多样性中受益。
  • @DarinDimitrov:仅供参考,用速度测试编辑了我的帖子(在BitConverter 的支持下)。
  • 您的解决方案没有考虑到通常您需要写入现有数组。在这种情况下,BitConverter 要慢得多,因为您必须创建一个新数组然后复制它。请看我的回答,我已经比较了不同的方法。
【解决方案2】:

请注意,BitConverter 可能不是最快的,如下面的测试所示。

使用BitConverter 类,特别是采用Int32 参数的GetBytes 方法:

var myInt = 123;
var bytes = BitConverter.GetBytes(myInt);

您可以使用BitConverter.IsLittlEndian根据CPU架构确定字节顺序。


编辑:由于编译器优化,下面的测试不是结论性的。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    [StructLayout(LayoutKind.Explicit)]
    struct FooUnion
    {
        [FieldOffset(0)]
        public byte byte0;
        [FieldOffset(1)]
        public byte byte1;
        [FieldOffset(2)]
        public byte byte2;
        [FieldOffset(3)]
        public byte byte3;

        [FieldOffset(0)]
        public int integer;
    }
    class Program
    {
        static void Main(string[] args)
        {
            testUnion();
            testBitConverter();

            Stopwatch Timer = new Stopwatch();

            Timer.Start();
            testUnion();
            Timer.Stop();

            Console.WriteLine(Timer.ElapsedTicks);

            Timer = new Stopwatch();

            Timer.Start();
            testBitConverter();
            Timer.Stop();

            Console.WriteLine(Timer.ElapsedTicks);
            Console.ReadKey();
        }

        static void testBitConverter()
        {
            byte[] UnionBytes;

            for (int i = 0; i < 10000; i++)
            {
                UnionBytes = BitConverter.GetBytes(i);
            }
        }

        static void testUnion()
        {
            byte[] UnionBytes;

            for (int i = 0; i < 10000; i++)
            {
                FooUnion union = new FooUnion() { integer = i };

                UnionBytes = new byte[] { union.byte0, union.byte1, union.byte2, union.byte3 };

            }
        }
    }
}

【讨论】:

  • 太慢了,抱歉。有更快的方法不涉及任何方法。
  • 查看我的编辑以进行速度测试,不确定我是否犯了错误,但不要这么认为。它表明如果你需要一个数组,BitConverter 会更快(虽然不是很多)。
  • 在原始帖子中哪里说开头有一个数组?它说4个字节。我使用结构偏移来处理金融市场数据(8 字节结构中的 5 字节 - 4 字节滴答计数,1 字节滴答编码)。快得离谱。
  • @adrift,你看到这个的原因是因为在发布模式下,编译器已经优化了代码并从 MSIL 中删除了 UnionBytes = new byte[] { union.byte0, union.byte1, union.byte2, union.byte3 }; 行,导致联合解决方案无用。这就是它在发布模式下快 10 倍的原因。因为它从不运行。
  • 看起来道德是,在实际代码中实现每一个并在那里测试。
【解决方案3】:

使用不安全代码的字节*转换是迄今为止最快的:

    unsafe static void Main(string[] args) {
        int i = 0x12345678;
        byte* pi = (byte*)&i;
        byte lsb = pi[0];  
        // etc..
    }

BitConverter 也是这样做的,这段代码避免了创建数组的成本。

【讨论】:

  • ;) 实际上它是不安全的 - 这不是必需的 ;) 真的。 Anotehr 安全 (!) 解决方案具有相似的速度。
  • 是的,这就是它使用 unsafe 关键字的原因。这几乎可以做到这一点,编写快速代码。 OP 的要求是fast,而不是similar
  • 很有趣,但这并不会改变这样一个事实,即对于这个特定问题,有一个既快速又安全的解决方案。因此,不安全的解决方案更糟。这对于其他问题是不同的,但是这个特定的问题会在我对不安全代码的审查中获得第二名 - 通过,请重写。
  • 我猜你不为微软工作。包含结构的方法的问题在于它们没有被内联。至少 BitConverter.GetBytes() 被内联。
  • 使用unsafe 是完全安全的,前提是您可以在受信任的上下文中运行,并且只要有某种方式表明如果不仔细检查它可能不安全。就像在代码附近的某处使用“不安全”一词作为一种指示器。
【解决方案4】:

最快的方法是使用包含 4 个字节的结构。

  • 在定义的布局中(字节位置 0、1、2、3
  • 还有一个从位置 0 开始的 int32。
  • 放入4个变量,读出字节。
  • 完成。

比 BitConverter 快得多。

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.aspx

具有必要的属性。

[StructLayout(LayoutKind.Explicit)]
struct FooUnion
{
    [FieldOffset(0)]
    public byte byte0;
    [FieldOffset(1)]
    public byte byte1;
    [FieldOffset(2)]
    public byte byte2;
    [FieldOffset(3)]
    public byte byte3;

    [FieldOffset(0)]
    public int integer;

}

【讨论】:

  • 我喜欢你聪明的解决方案
  • 我也是...我只是希望它更容易阅读(答案,而不是代码)并且有一些基准测试。无论如何都要 +1
  • 干净、简单、易懂。当代码是防弹的时候,我不在乎性能。 ++
  • 如果您不仅要转换字节,而且还需要打包和解包的值供以后使用,那么此代码是完美的。例如,在处理颜色时,将 R、G、B 和 Alpha byte 值存储到 int 中。
  • 这是一种我以前从未考虑过/见过的惊人技术。 ++
【解决方案5】:
class Program
{
    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        unsafe{
            byte[] byteArray = new byte[4];
            for(int i = 0; i != int.MaxValue; ++i)
            {
            fixed(byte* asByte = byteArray)
               *((int*)asByte) = 43;
               }
        }
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.Read();
    }
}

在我的机器上平均大约 2770 毫秒

[StructLayout(LayoutKind.Explicit)]
struct Switcher
{
  [FieldOffset(0)]
  public int intVal;
  [FieldOffset(0)]
  public byte b0;
  [FieldOffset(1)]
  public byte b1;
  [FieldOffset(2)]
  public byte b2;
  [FieldOffset(3)]
  public byte b3;
}
class Program
{
    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        byte[] byteArray = new byte[4];
        Switcher swi = new Switcher();
        for(int i = 0; i != int.MaxValue; ++i)
        {
          swi.intVal = 43;
          byteArray[0] = swi.b0;
          byteArray[1] = swi.b1;
          byteArray[2] = swi.b2;
          byteArray[3] = swi.b3;
        }
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.Read();
    }
}

平均大约 4510 毫秒。

【讨论】:

  • 假测试 - 符合新条件。最初没有关于数组的词,并且您将指针 arithmethid 替换为经过安全边界代码测试的数组 - 因此测试偏向于数组边界检查,对结构解决方案不利。
  • @TomTom 当然这是一个假测试,所有测试都是假的,除了看看它在现实生活中是如何工作的。两者都没有针对其中一个进行优化,因此它可以服务。我们可以将数组副本替换为将其放入四个单独的字节中,并且将字节实际获取到某处添加到基于结构的方法中仍然很重要。对于无法使用 unsafe 的情况,这是一个很好的解决方法,而且它的速度很快,大约每秒十亿个周期,不会有任何困难。
【解决方案6】:

我研究了将基本类型序列化为字节数组所需的时间。当您已经有一个数组和要放置数据的偏移量时,我这样做了。我想这确实是一个重要的案例,与理论上得到一个 4 字节的数组相比,因为当你序列化某些东西时,它正是你所需要的。我发现哪种方法更快的答案取决于您要序列化的类型。我尝试了几种方法:

  1. 带有额外缓冲区溢出检查的不安全引用
  2. GetBytes + consequent Buffer.BulkCopy(这与 1 加开销基本相同)
  3. 使用移位直接赋值 ( m_Bytes[offset] = (byte)(value &gt;&gt; 8)
  4. 使用移位和按位 & 直接赋值 m_Bytes[offset] = (byte)((i &gt;&gt; 8) &amp; 0xFF)

我将所有测试运行了 1000 万次。以下是以毫秒为单位的结果

Long Int Short Byte Float Double 1 29 32 31 30 29 34 2 209 233 220 212 208 228 3 63 24 13 8 24 44 4 72 29 14

正如您所见,不安全的方式对于 long 和 double 来说要快得多(未签名版本与签名版本大致相同,因此它们不在表中)。对于short/int/float,最快的方法是使用移位的2/4/4 赋值。对于字节来说,最快的显然是简单的赋值。因此,关于原始问题-分配方式是最好的。这是以最快的方式实现此类功能的示例:

    public static void WriteInt(byte[] buffer, int offset, int value)
    {
        m_BytesInt[offset] = (byte)(value >> 24);
        m_BytesInt[offset + 1] = (byte)(value >> 16);
        m_BytesInt[offset + 2] = (byte)(value >> 8);
        m_BytesInt[offset + 3] = (byte) value;
    }

附:测试在 x64 环境中运行,代码在发布模式下编译为 cpu any(运行时为 x64)。

【讨论】:

  • ++offset 会加快速度吗?
  • @Karussell 你可能是对的,由于某种原因我没有考虑过,但你可以在这里查看并分享结果:)
【解决方案7】:

我认为这可能是 C# 中最快的方法。(将字节数组初始化为带 int32 的 int 流的 4 倍

        private MemoryStream Convert(int[] Num, byte[] Bytes)
    {
        Buffer.BlockCopy(Num, 0, Bytes, 0, Bytes.Length);
        MemoryStream stream = new MemoryStream(Bytes);
        return stream;
    }

【讨论】:

    【解决方案8】:

    这里的许多人似乎都在争论 BitConverter 是否比专用的 struct 更好。基于 BCL 源代码,BitConverter.GetBytes() 如下所示:

    public static unsafe byte[] GetBytes(int value)
    {
        byte[] buffer = new byte[4];
        fixed (byte* bufferRef = buffer)
        {
            *((int*)bufferRef) = value;
        }
        return buffer;
    }
    

    从我的角度来看,这比将 1 个整数 + 4 个字节分配给像这个这样的显式结构更简洁且似乎更快。

    [StructLayout(LayoutKind.Explicit)]
    struct IntByte
    {
      [FieldOffset(0)]
      public int IntVal;
      [FieldOffset(0)]
      public byte Byte0;
      [FieldOffset(1)]
      public byte Byte1;
      [FieldOffset(2)]
      public byte Byte2;
      [FieldOffset(3)]
      public byte Byte3;
    }
    
    new IntByte { IntVal = 10 } -> Byte0, Byte1, Byte2, Byte3.
    

    【讨论】:

      【解决方案9】:

      联合是将整数拆分为字节的最快方法。下面是一个完整的程序,其中 C# 优化器无法优化字节拆分操作,因为每个字节都被求和并打印出来。

      我的笔记本电脑上的计时是 Union 为 419 毫秒BitConverter 为 461 毫秒。但是,速度增益要大得多。

      此方法用于开源高性能算法HPCsharp 库,其中 Union 方法为基数排序提供了很好的性能提升。

      Union 更快,因为它不执行按位掩码和移位,而只是从 4 字节整数中读取正确的字节。

      using System;
      using System.Diagnostics;
      using System.Runtime.InteropServices;
      
      namespace SplitIntIntoBytes
      {
          [StructLayout(LayoutKind.Explicit)]
          struct FooUnion
          {
              [FieldOffset(0)]
              public byte byte0;
              [FieldOffset(1)]
              public byte byte1;
              [FieldOffset(2)]
              public byte byte2;
              [FieldOffset(3)]
              public byte byte3;
      
              [FieldOffset(0)]
              public int integer;
          }
          class Program
          {
              static void Main(string[] args)
              {
                  testUnion();
                  testBitConverter();
      
                  Stopwatch Timer = new Stopwatch();
      
                  Timer.Start();
                  int sumTestUnion = testUnion();
                  Timer.Stop();
      
                  Console.WriteLine("time of Union:        " + Timer.ElapsedTicks + " milliseconds,  sum: " + sumTestUnion);
      
                  Timer.Restart();
                  int sumBitConverter = testBitConverter();
                  Timer.Stop();
      
                  Console.WriteLine("time of BitConverter: " + Timer.ElapsedTicks + " milliseconds,  sum: " + sumBitConverter);
                  Console.ReadKey();
              }
      
              static int testBitConverter()
              {
                  byte[] UnionBytes = new byte[4];
                  byte[] SumOfBytes = new byte[4];
                  SumOfBytes[0] = SumOfBytes[1] = SumOfBytes[2] = SumOfBytes[3] = 0;
      
                  for (int i = 0; i < 10000; i++)
                  {
                      UnionBytes = BitConverter.GetBytes(i);
                      SumOfBytes[0] += UnionBytes[0];
                      SumOfBytes[1] += UnionBytes[1];
                      SumOfBytes[2] += UnionBytes[2];
                      SumOfBytes[3] += UnionBytes[3];
                  }
                  return SumOfBytes[0] + SumOfBytes[1] + SumOfBytes[2] + SumOfBytes[3];
              }
      
              static int testUnion()
              {
                  byte[] UnionBytes;
                  byte[] SumOfBytes = new byte[4];
                  SumOfBytes[0] = SumOfBytes[1] = SumOfBytes[2] = SumOfBytes[3] = 0;
      
                  FooUnion union = new FooUnion();
      
                  for (int i = 0; i < 10000; i++)
                  {
                      union.integer = i;
                      UnionBytes = new byte[] { union.byte0, union.byte1, union.byte2, union.byte3 };
                      SumOfBytes[0] += UnionBytes[0];
                      SumOfBytes[1] += UnionBytes[1];
                      SumOfBytes[2] += UnionBytes[2];
                      SumOfBytes[3] += UnionBytes[3];
                  }
                  return SumOfBytes[0] + SumOfBytes[1] + SumOfBytes[2] + SumOfBytes[3];
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-11
        • 1970-01-01
        • 2020-03-01
        • 2020-01-20
        • 1970-01-01
        • 2011-02-19
        • 2011-05-18
        • 2013-02-17
        • 2014-11-29
        相关资源
        最近更新 更多