【问题标题】:Fast casting in C# using BitConverter, can it be any faster?使用 BitConverter 在 C# 中快速转换,它可以更快吗?
【发布时间】:2011-06-22 20:53:42
【问题描述】:

在我们的应用程序中,我们有一个非常大的字节数组,我们必须将这些字节转换为不同的类型。目前,我们为此使用BitConverter.ToXXXX()。我们的重头戏是ToInt16ToUInt64

对于UInt64,我们的问题是数据流实际上有6个字节的数据来表示一个大整数。由于没有将 6 字节数据转换为 UInt64 的本机函数,我们这样做:

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

我们对ToInt16的使用更简单,不需要做任何位操作。

我们做了很多这 2 项操作,因此我想询问 SO 社区是否有更快的方法来进行这些转换。目前,这两个函数消耗了我们整个 CPU 周期的大约 20%。

【问题讨论】:

  • 整数性能不太可能是您的问题。处理大型阵列几乎总是使慢速 RAM 总线成为瓶颈。注意分析器输出中的“Last Level Cache Misses”性能计数器。
  • @Hans:你绝对正确:我们受内存限制。但为此,我不知道该怎么做。我们有一个大数组,我们必须遍历每个字节来提取数据。当我线性进入数组时,硬件预取器可能会锁定访问模式,除此之外,我不知道还能做什么。 --谢谢

标签: c# .net casting bitconverter


【解决方案1】:

为什么不:

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

尽管 JIT 编译器可能会自动为您执行此操作,但您可以将其设为一条语句。

这将阻止您读取最终丢弃的额外两个字节。

如果这不会减少 CPU,那么您可能需要编写自己的转换器,直接从缓冲区读取字节。您可以使用数组索引,或者,如果您认为有必要,可以使用带有指针的不安全代码。

请注意,正如评论者指出的那样,如果您使用这些建议中的任何一个,那么您要么受限于特定的“字节序”,要么您必须编写代码来检测小/大字节序并做出相应的反应。我上面展示的代码示例适用于 little endian (x86)。

【讨论】:

  • 您应该提到这适用于给定的字节顺序(我认为很少,但我总是将两者混为一谈)。它可能对 OP 很重要,也可能无关紧要。
  • 我遵循了您最初的建议,认为读取这 2 个额外的字节并将它们丢弃一定会减慢我的速度,但事实证明,这是最慢的方法。我猜由于数据已经被缓存,因此一次读取 8 或 6 个字节并不重要。您的第二个建议,@Jimmy 作为答案提供的代码运行得更快。 -- 谢谢
【解决方案2】:

您是否考虑过直接使用内存指针。我不能保证它的性能,但它是 C++\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));
        }

您需要在构建设置中使您的程序集“不安全”,并标记您执行此操作的方法是不安全的。使用这种方法,您还与 little endian 相关联。

【讨论】:

  • 事实证明这是最快的方法,至少到目前为止是这样。
  • 小心这个。如果您想在数组末尾读取其中一个 6 字节数字,您将遇到异常。也就是说,如果在上面的示例中,数组只有 12 个字节长,那么在读取第二个值时会出现异常。
【解决方案3】:

您可以使用System.Buffer 类将整个数组复制到另一个不同类型的数组,作为快速'block copy' 操作:

BlockCopy 方法使用内存中的偏移量访问 src 参数数组中的字节,而不是诸如索引或数组上下界之类的编程构造。

数组类型必须是“原始”类型,它们必须对齐,并且复制操作是字节序敏感的。在您使用 6 字节整数的情况下,它不能与 .NET 的任何“原始”类型对齐,除非您可以获得每六个填充两个字节的源数组,然后将对齐到 Int64。但是这种方法适用于Int16的数组,可能会加快你的一些操作。

【讨论】:

  • 感谢 System.Buffer.BlockCopy 信息。在我们的例子中,UInt64 和 Int16 在数组中是交错的,所以 BlockCopy 对我们不起作用,但是这个信息很有帮助,我们以后可以使用这个方法。
【解决方案4】:

查看我对类似问题的回答here。 这与吉米的回答中的不安全内存操作相同,但对消费者来说更“友好”。它允许您将byte 数组视为UInt64 数组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-30
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    相关资源
    最近更新 更多