【问题标题】:Is this the most optimal way? C bitfields这是最优化的方式吗? C 位域
【发布时间】:2011-01-25 23:58:18
【问题描述】:

我创建了一个函数来设置或清除 DWORD 中的特定位数。我的功能有效。我不需要帮助让它工作。但是,我想知道我选择的方法是否是最快的方法。

我很难解释这是如何工作的。有两个包含 DWORD 的数组,它们在 DWORD 的左侧和右侧填充了位(所有二进制 1)。它制作了一个掩码,除了我要设置或清除的位之外的所有位都已填充,然后使用基于该掩码的按位运算符设置它们。对于这样一个简单的任务,它似乎相当复杂,但它似乎是我能想到的最快方法。这比一点一点设置要快得多。

static DWORD __dwFilledBitsRight[] = {
        0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF,    0x7FFF, 0xFFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF
    };

static DWORD __dwFilledBitsLeft[] = {
        0x0, 0x80000000, 0xC0000000, 0xE0000000, 0xF0000000, 0xF8000000, 0xFC000000, 0xFE000000, 0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000, 0xFFF00000, 0xFFF80000, 0xFFFC0000, 0xFFFE0000,    0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000, 0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0, 
        0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF
    };

    // nStartBitFromLeft must be between 1 and 32... 
    // 1 is the bit farthest to the left (actual bit 31)
    // 32 is the bit farthest to the right (actual bit 0)
    inline void __FillDWORDBits(DWORD *p, int nStartBitFromLeft, int nBits, BOOL bSet)
    {
        DWORD dwLeftMask = __dwFilledBitsLeft[nStartBitFromLeft - 1]; // Mask for data on the left of the bits we want
        DWORD dwRightMask = __dwFilledBitsRight[33 - (nStartBitFromLeft + nBits)]; // Mask for data on the right of the bits we want
        DWORD dwBitMask = ~(dwLeftMask | dwRightMask); // Mask for the bits we want
        DWORD dwOriginal = *p;
        if(bSet) *p = (dwOriginal & dwLeftMask) | (dwOriginal & dwRightMask) | (0xFFFFFFFF & dwBitMask);
        else *p = (dwOriginal & dwLeftMask) | (dwOriginal & dwRightMask) | 0;

    }

【问题讨论】:

  • 我也是这么想的……但我的目标平台是i386+
  • 就个人而言,当我觉得有些事情很难向其他人解释时,我会将其视为一个警告信号。 :-) 您可能应该对您的代码进行基准测试,并与设置进行比较(如果您还没有的话)。您是否尝试过使用联合?
  • 是的,我不相信我在 Windows 中使用时钟滴答的基准测试...但是根据指令的数量,逐位设置在整个迭代过程中会有更多的指令。
  • 作为旁注,您的 __Fi... 名称给出了未定义的行为(以下划线开头的名称后跟另一个下划线或大写字母保留用于实现)。就个人而言,我会将其分为两个函数,fill_bitsclear_bits。将bool 作为参数传递通常是一个错误——如果您坚持使用这种结构,请使用change_bits 之类的内容并传递enum { CLEAR, FILL};。作为参数 - 但单独的功能会更好,IMO。
  • 有趣...我通常对这些函数使用两个下划线,这样我可以在我的函数列表中更快地找到它们...我正在努力理解您在这里所说的...我不喜欢 unix 风格的命名约定(即lower_lower())。就我个人而言,我认为它们使我的程序可读性降低,而且更加神秘。

标签: c bit-fields bitflags


【解决方案1】:

怎么样:

// Create mask of correct length, and shift to the correct position
DWORD mask = ((1ULL << nBits) - 1) << pos;
// Apply mask (or its inverse)
if (bSet)
{
    *p |= mask;
}
else
{
    *p &= ~mask;
}

在任何现代处理器上,简单的按位运算很可能比查表更快。

注意:根据本平台DWORDlong long的关系,nBits == sizeof(DWORD)*8的情况可能需要特殊处理。或者如果nBits==0 是不可能的,你可以只做DWORD mask = ((2ULL &lt;&lt; (nBits - 1)) - 1) &lt;&lt; pos;

更新: 有人提到if 可能会很慢,这是事实。这是它的替代品,但您需要测量一下它在实践中是否真的更快。

// A bit hacky, but the aim is to get 0x00000000 or 0xFFFFFFFF
// (relies on two's-complement representation)
DWORD blanket = bSet - 1;
// Use the blanket to override one or other masking operation
*p |=  (blanket | mask);
*p &= ~(blanket & mask);

【讨论】:

  • 你应该注意1L &lt;&lt; nBits是未定义的nBits等于单词的长度,在这种情况下是32。如果 32(或 64)是有效参数,则必须编写特殊情况。
  • if 会很慢。最好避免它。
  • 太棒了...这是我一开始就在脑子里想的代码,但是我无法编码,所以我最终得到了那个查找表怪物...谢谢一群老兄:)
  • @Giuseppe:确实,如果sizeof(long) &lt;= sizeof(DWORD)。碰巧我已经更新了我的答案来提及这一点!
  • @PaulB:这不仅仅是汇编器是否更小。这也是执行查找的内存访问时间。
【解决方案2】:

这就是我的做法。我将它分成两个函数,setbits() 和 clearbits()。为清晰起见,我们对步骤进行了细分,我相信它可以得到更好的优化。

这个版本依赖于 32 位代码。此外,在我的世界中,位 0 是最右边的位。您的里程可能会有所不同。

setbits( DWORD *p , int offset , int len )
{
  // offset must be 0-31, len must be 0-31, len+offset must be 0-32
  int   right_shift = ( !len ? 0 : 32 - (len+offset) ) ;
  int   left_shift  = offset ;
  DWORD right_mask  = 0xFFFFFFFF >> right_shift  ;
  DWORD left_mask   = 0xFFFFFFFF << left_shift   ;
  DWORD mask        = left_mask & right_mask     ;

  *p |= mask ;

  return ;
}

clearbits( DWORD *p , int offset , int len )
{
  // offset must be 0-31, len must be 0-31, len+offset must be 0-32
  int   right_shift = ( !len ? 0 : 32 - (len+offset) ) ;
  int   left_shift  = offset ;
  DWORD right_mask  = 0xFFFFFFFF >> right_shift   ;
  DWORD left_mask   = 0xFFFFFFFF << left_shift    ;
  DWORD mask        = ~( left_mask & right_mask ) ;

  *p &= mask ;

  return ;
}

我今天在寻找其他东西时偶然发现了这个改进的版本。感谢斯坦福大学 Sean Anderson 的Bit Twiddling Hacks

// uncomment #define to get the super scalar CPU version.
// #define SUPER_SCALAR_CPU
void setbits( unsigned int *p , int offset , int len , int flag )
{
  unsigned int mask = ( ( 1 << len ) - 1 ) << offset ;

#if !defined( SUPER_SCALAR_CPU )
  *p ^= ( - flag ^ *p ) & mask ;
#else
  // supposed to be some 16% faster on a Intel Core 2 Duo than the non-super-scalar version above
  *p = (*p & ~ mask ) | ( - flag & mask ) ;
#endif

  return ;

}

不过,很大程度上取决于您的编译器。

【讨论】:

  • 是的,我喜欢这个...对)
  • 非常好,但取决于上下文。当bool bSet 是编译时间常数时,显式调度是最好的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
  • 2013-06-30
相关资源
最近更新 更多