c# - 最快的方法从部分位获取整数

Posted

技术标签:

【中文标题】c# - 最快的方法从部分位获取整数【英文标题】:c# - fastest method get integer from part of bits 【发布时间】:2020-12-26 12:43:24 【问题描述】:

我有byte[] byteArray,通常是byteArray.Length = 1-3

我需要将数组分解为位,取一些位(例如,5-17),然后将这些位转换为 Int32。

我试过这样做

private static IEnumerable<bool> GetBitsStartingFromLSB(byte b)

    for (int i = 0; i < 8; i++)
    
        yield return (b % 2 != 0);
        b = (byte)(b >> 1);
    

public static Int32 Bits2Int(ref byte[] source, int offset, int length)

    List<bool> bools = source.SelectMany(GetBitsStartingFromLSB).ToList();
    bools = bools.GetRange(offset, length);
    bools.AddRange(Enumerable.Repeat(false, 32-length).ToList() );
    int[] array = new int[1];
    (new BitArray(bools.ToArray())).CopyTo(array, 0);
    return array[0];           

但是这个方法太慢了,不得不经常调用。

我怎样才能更有效地做到这一点?

非常感谢!现在我这样做:

public static byte[] GetPartOfByteArray(  byte[] source, int offset, int length)
    
        byte[] retBytes = new byte[length];
        Buffer.BlockCopy(source, offset, retBytes, 0, length);
        return retBytes;
    
    public static Int32 Bits2Int(byte[] source, int offset, int length)
    
        if (source.Length > 4)
        
            source = GetPartOfByteArray(source, offset / 8, (source.Length - offset / 8 > 3 ? 4 : source.Length - offset / 8));
            offset -= 8 * (offset / 8);
        
        byte[] intBytes = new byte[4];
        source.CopyTo(intBytes, 0);
        Int32 full = BitConverter.ToInt32(intBytes);
        Int32 mask = (1 << length) - 1;
        return (full >> offset) & mask;
    

而且运行速度非常快!

【问题讨论】:

我认为这个问题更适合 codereview.stackexchange.com @Fildor BitConverter 在这种情况下基本上是零使用;和 IMO 几乎任何其他;事实上,BitConverter 上唯一真正有用的方法是 .ToString(),作为在单元测试等中编写 hex 的一种懒惰方式,也许 .IsLittleEndian 可以查询 CPU 字节序跨度> @MarcGravell 不幸的是,我不得不同意。 【参考方案1】:

如果您追求“快速”,那么最终您需要使用位逻辑来执行此操作,而不是 LINQ 等。我不会编写实际代码,但您需要:

使用/ 8 % 8 的偏移量来查找起始字节 和该字节内的位偏移量 无论您需要多少字节,都可以组合 - 如果您使用 32 位数字,则很可能最多 5 个(因为可能存在偏移) ;例如进入long,无论您期望采用哪种字节序(可能是大字节序?) 对组合值使用右移 (&gt;&gt;) 以删除应用位偏移所需的许多位(即value &gt;&gt;= offset % 8;) 屏蔽掉你不想要的任何位;例如value &amp;= ~(-1L &lt;&lt; length);-1 给你全一;&lt;&lt; length 在右手边创建length 零,~ 将所有零交换为一,将一交换为零,所以你现在有length 右边的) 如果值是有符号的,您需要考虑如何处理负数,尤其是在您不总是读取 32 位的情况下

【讨论】:

【参考方案2】:

首先,您要求优化。但你所说的只有:

太慢了 需要经常调用它

没有关于:

的信息 慢多少才算太慢?你测量过当前的代码吗?您估计过需要多快吗? “经常”的频率是多少? 源字节数组有多大? 等

可以通过多种方式进行优化。当要求优化时,一切都很重要。例如,如果 source byte[] 是 1 或 2 个字节长(是的,可能很荒谬,但你没有告诉我们),如果它很少改变,那么你可以获得非常好的结果通过缓存结果。以此类推。

所以,我没有解决方案,只是列出了可能的性能问题:

private static IEnumerable<bool> GetBitsStartingFromLSB(byte b) // A

    for (int i = 0; i < 8; i++)
    
        yield return (b % 2 != 0); // A
        b = (byte)(b >> 1);
    

public static Int32 Bits2Int(ref byte[] source, int offset, int length)

    List<bool> bools = source.SelectMany(GetBitsStartingFromLSB).ToList(); //A,B
    bools = bools.GetRange(offset, length); //B
    bools.AddRange(Enumerable.Repeat(false, 32-length).ToList() ); //C
    int[] array = new int[1]; //D
    (new BitArray(bools.ToArray())).CopyTo(array, 0); //D
    return array[0]; //D

答:LINQ 很有趣,但除非小心操作,否则不会很快。对于每个输入字节,它需要 1 个字节,将其拆分为 8 个布尔值,然后将它们传递给编译器生成的 IEnumerable 对象 *)。请注意,这一切也需要稍后清理。可能您只需返回new bool[8] 甚至BitArray(size=8) 即可获得更好的性能。

*) 从概念上讲。实际上yield-return是惰性的,所以它不是8valueobj+1refobj,而只是1个可枚举的生成项目。但是,你在 (B) 中执行 .ToList(),所以我以这种方式写这篇文章与事实相去甚远。

A2:8 是硬编码的。一旦你放弃了漂亮的 IEnumerable 并将其更改为类似常量大小的数组,你可以预先分配该数组并通过参数将其传递给 GetBitsStartingFromLSB 以进一步减少创建和随后丢弃的临时对象的数量。而且由于 SelectMany 一个接一个地访问项目而无需返回,因此可以重用该预分配的数组。

B:将整个 Source 数组转换为字节流,将其转换为 List。然后丢弃整个列表,除了该列表的一小部分偏移长度范围。为什么要隐蔽列出呢?这只是浪费了另一组对象,并且内部数据也被复制了,因为bool 是一个值类型。您可以通过 .Skip(X).Take(Y) 直接从 IEnumerable 获取范围

C:填充布尔列表以包含 32 个项目。 AddRange/Repeat 很有趣,但 Repeat 必须返回一个 IEnumerable。它又是另一个被创建并丢弃的对象。您正在使用false 填充列表。放弃列表的想法,使其成为 bool[32]。或位数组(32)。它们自动以false 开头。这是bool 的默认值。遍历“范围”A+B 中的那些位,并按索引将它们写入该数组。那些书面的将有它们的价值,那些不成文的将保持虚假。工作完成,没有浪费任何东西。

C2:将预分配的 32 项数组与 A+A2 连接。 GetBitsStartingFromLSB 不需要返回任何东西,它可能会通过参数获得一个要填充的缓冲区。并且该缓冲区不需要是 8 项缓冲区。您可以传递整个 32 项的最终数组,并传递一个偏移量,以便该函数准确地知道在哪里写入。浪费的物品更少。

D:最后,所有将选定位作为整数返回的工作。新的临时数组被创建和浪费,新的 BitArray 也被有效地创建和浪费。请注意,之前您已经在 GetBitsStartingFromLSB 中进行了手动位移转换 int->bits,为什么不创建一个类似的方法来进行一些位移并改为使用 bits->int?如果您知道位的顺序,那么您现在也知道它们了。不需要数组和位数组,不需要一些代码摆动,并且您可以再次节省分配和数据复制。

我不知道这将为您节省多少时间/空间/等,但这只是乍一看突出的几点,无需过多地修改您对代码的原始想法,无需全部完成一口气通过数学和位移等。我已经看到 MarcGravell 也已经给你写了一些提示。如果您有空闲时间,我建议您先一个一个地尝试,看看每个更改如何(如果有的话!)每个更改都会影响性能。只是去看看。然后,您可能会放弃这一切,并在 Marc 的提示下再次尝试新的“通过数学和位移一次性完成所有操作”版本。

【讨论】:

以上是关于c# - 最快的方法从部分位获取整数的主要内容,如果未能解决你的问题,请参考以下文章

获取十进制数的整数部分的最佳方法

c#中Array类中的常用方法的功能

从原生 C++ 重构代码位的选项?

在C中找到整数中最高设置位(msb)的最快/最有效方法是啥?

在C中找到整数中最高设置位(msb)的最快/最有效方法是啥?

使用 192/256 位整数对无符号 64 位整数向量的点积求和的最快方法?