操纵C中的位。有更好的方法吗?

时间:2014-06-02 10:28:30

标签: c bit-manipulation

我有一个程序使用以下两个函数99.9999%的时间:

unsigned int getBit(unsigned char *byte, unsigned int bitPosition)
{
    return (*byte & (1 << bitPosition)) >> bitPosition;
}


void setBit(unsigned char *byte, unsigned int bitPosition, unsigned int bitValue)
{
    *byte = (*byte | (1 << bitPosition)) ^ ((bitValue ^ 1) << bitPosition);
}

这可以改善吗?程序的处理速度主要取决于这两个函数的速度。

更新
我会为每个提供的答案做一个基准测试并写出我得到的时间。作为参考,使用的编译器是Mac OS X平台上的gcc:

Apple LLVM 5.1版(clang-503.0.40)(基于LLVM 3.4svn)

我编译时没有任何具体的论据,如: gcc -o program program.c
如果您认为我应该进行一些优化,请随时提出建议。

CPU是:
2,53 GHz Intel Core 2 Duo

使用我最初提供的功能处理21.5 MB的数据时,需要: 时间:13.565221
时间:13.558416
时间:13.566042

时间以秒为单位(这是三次尝试)。

- 更新2 -

我使用了-O3优化( gcc -O3 -o program program.c )选项,现在我得到了这些结果:
时间:6.168574
时间:6.170481
时间:6.167839

我现在重做其他基准......

4 个答案:

答案 0 :(得分:4)

如果你想坚持使用功能,那么第一个:

unsigned int getBit(unsigned char *byte, unsigned int bitPosition)
  {
  return (*byte >> bitPosition) & 1;
  }

对于第二个:

void setBit(unsigned char *byte, unsigned int bitPosition, unsigned int bitValue)
  {
  if(bitValue == 0)
    *byte &= ~(1 << bitPosition);
  else
    *byte |= (1 << bitPosition);
  }

但是,我怀疑函数调用/返回开销将淹没实际的位翻转。一个好的编译器可能会内联这些函数调用,但是你可以通过将它们定义为宏来获得一些改进:

#define getBit(b, p) ((*(b) >> (p)) & 1)

#define setBit(b, p, v) (*(b) = ((v) ? (*(b) | (1 << (p))) : (*(b) & (~(1 << (p))))))

@ user694733指出分支预测可能是一个问题,可能会导致速度减慢。因此,定义单独的setBitclearBit函数可能会很好:

void setBit(unsigned char *byte, unsigned int bitPosition)
  (
  *byte |= (1 << bitPosition);
  }

void clearBit(unsigned char *byte, unsigned int bitPosition)
  (
  *byte &= ~(1 << bitPosition);
  }

及其相应的宏版本:

#define setBit(b, p) (*(b) |= (1 << (p)))

#define clearBit(b, p) (*(b) &= ~(1 << (p)))

如果调用代码对原始版本中bitValue参数传递的值进行硬编码,则单独的函数/宏将非常有用。

分享并享受。

答案 1 :(得分:2)

怎么样:

bool getBit(unsigned char byte, unsigned int bitPosition)
{
    return (byte & (1 << bitPosition)) != 0;
}

无需使用班次操作员来实现&#34;将屏蔽位移到位置0,只需使用比较运算符并让编译器处理它。如果可能的话,这当然也应该是内联的。

对于第二个,它基本上是&#34; assignBit&#34;这一事实使它变得复杂,即它将指示位的新值作为参数。我尝试使用显式分支:

unsigned char setBit(unsigned char byte, unsigned int bitPosition, bool value)
{
  const uint8_t mask = 1 << bitPosition;
  if(value)
    return byte | mask;
  return byte & ~mask;
}

答案 2 :(得分:2)

通常,这些东西最好留给编译器的优化器。

但为什么你需要这些琐碎任务的功能呢?当C程序员遇到这样的基本内容时,不应该感到震惊:

x |= 1<<n;      // set bit
x &= ~(1<<n);   // clear bit
x ^= 1<<n;      // toggle bit
y = x & (1<<n); // read bit

没有真正的理由隐藏功能背后的简单事物。你不会让代码更具可读性,因为你总是可以假设你的代码的读者知道C.它看起来像是无意义的包装函数来隐藏&#34;可怕的&#34;程序员不熟悉的操作员。

话虽这么说,功能的引入可能会导致很多开销代码。要将函数重新转换为上面显示的核心操作,优化器必须非常好。


如果由于某种原因坚持使用这些功能,任何手动优化的尝试都将成为一个值得怀疑的做法。使用inlineregister和此类关键字可能是多余的。启用优化器的编译器应该能够更好地决定何时内联以及何时将内容放入寄存器而不是程序员。

像往常一样,手动优化代码没有意义,除非您比给编写编译器端口的人更了解给定的CPU。通常情况并非如此。

可以无害地做什么作为手动优化,就是去掉unsigned char(你无论如何也不应该使用本机C类型)。而是使用stdint.h中的uint_fast8_t类型。使用这种类型意味着:&#34;我希望有一个uint8_t,但如果CPU因为对齐/性能原因而更喜欢更大的类型,那么它可以使用它而不是#34;。


修改

将位设置为1或0有多种方法。为了最大可读性,您可以这样写:

uint8_t val = either_1_or_0;
...

if(val == 1)
  byte |= 1<<n;
else
  byte &= ~(1<<n);

但这包括一个分支。让我们假设我们知道分支是给定系统上已知的性能瓶颈,以证明手动优化的其他可疑实践。然后,我们可以按照以下方式将该位设置为1或0而不使用分支:

byte = (byte & ~(1<<n)) | (val<<n);

这就是代码变得有点难以理解的地方。阅读以上内容:

  • 取出字节并保留其中的所有内容,但我们要设置为1或0的位除外。
  • 清除这一点。
  • 然后将其设置为1或0。

请注意,如果val为零,则整个右侧子表达式毫无意义。所以在一个&#34;泛型系统&#34;此代码可能比可读版本慢。因此,在编写这样的代码之前,我们必须知道我们的CPU非常擅长于位翻转并且在分支预测方面不那么好。

答案 3 :(得分:1)

您可以使用以下变体进行基准测试,并保持最佳解决方案。

inline unsigned int getBit(unsigned char *byte, unsigned int bitPosition)
{
    const unsigned char mask = (unsigned char)(1U << bitPosition);
    return !!(*byte & mask);
}


inline void setBit(unsigned char *byte, unsigned int bitPosition, unsigned int bitValue)
{
    const unsigned char mask = (unsigned char)(1U << bitPosition);
    bitValue ? *byte |= mask : *byte &= ~mask;
}

如果您的算法仅预期来自getBit的零v / s非零结果,则可以从返回中删除!!。 (要返回01,我发现@BobJarvis的版本非常干净了)

如果您的算法可以通过要设置的位掩码或重置为setBit函数,则无需显式计算掩码。

因此,根据调用这些函数的代码,可以按时缩短。