比掩码位和手工提取数据更有效(计算)位字段?

我有许多小块数据,我希望能够推入一个更大的数据类型。 假设,假设这是一个日期和时间。 显而易见的方法是通过像这样的位字段。

struct dt { unsigned long minute :6; unsigned long hour :5; unsigned long day :5; unsigned long month :4; unsigned long year :12; }stamp; 

现在让我们假装这个东西是有序的,这样首先声明的东西比后面声明的东西具有更高的重要性,所以如果我用变量的第一个字母表示位,它看起来像:

 mmmmmm|hhhhh|ddddd|mmmm|yyyyyyyyyyyy 

最后,让我假装我只是声明一个unsigned long并使用mask将其拆分并进行相同的操作。

 unsigned long dateTime; 

这是我的问题:
在计算机需要做什么方面,以下是访问分钟,小时等等的方法吗? 或者是否存在编译器/计算机与位字段一起使用的一些棘手方法。

 unsigned minutes = stamp.minutes; //versus unsigned minutes = ((dateTime & 0xf8000000)>>26; 

 unsigned hours = stamp.hours; //versus unsigned hours = ((dateTime & 0x07C00000)>>21; 

等等

编译器生成与显式写入以访问位相同的指令。 所以不要指望它在位域上更快。

事实上,严格来说,对于位域,你无法控制它们在数据中的位置(除非你的编译器给你一些额外的保证。我的意思是C99标准没有定义任何)。 手动执行掩码,您至少可以将两个最常访问的字段放在系列中的第一个和最后一个,因为在这两个位置中,需要一个操作而不是两个操作来隔离字段。

那些可能会编译相同的机器代码,但如果它真的很重要,那就进行基准测试。 或者,更好的是,只需使用位域,因为它更容易!

快速测试gcc产量:

 shrq $6, %rdi ; using bit field movl %edi, %eax andl $31, %eax 

 andl $130023424, %edi ; by-hand shrl $21, %edi movl %edi, %eax 

这是一个小端机器,因此数字不同,但三条指令几乎相同。

它完全取决于平台和编译器。 某些处理器(尤其是微控制器)具有位寻址指令或位可寻址存储器,如果使用内置语言结构,编译器可以直接使用这些指令。 如果使用位掩码对这样的处理器上的位进行操作,编译器必须更聪明地发现潜在的优化。

在大多数桌面平台上,我建议你大汗淋漓,但如果你需要知道,你应该通过分析或计时代码来测试它,或分析生成的代码。 请注意,根据编译器优化选项,甚至不同的编译器,您可能会得到非常不同的结果。

在这个例子中,我会手动使用位字段。
不是因为访问。 但由于能够比较两个dt的。
最后,编译器将始终生成比您更好的代码(因为编译器会随着时间的推移变得更好并且永远不会出错)但是这段代码很简单,您可能会编写最佳代码(但这是您应该进行的微观优化)不要担心)。

如果你的dt是一个格式为的整数:

 yyyyyyyyyyyy|mmmm|ddddd|hhhhh|mmmmmm 

然后你可以自然地比较它们。

 dt t1(getTimeStamp()); dt t2(getTimeStamp()); if (t1 < t2) { std::cout << "T1 happened before T2\n"; } 

通过使用位字段结构,代码如下所示:

 dt t1(getTimeStamp()); dt t2(getTimeStamp()); if (convertToInt(t1) < convertToInt(t2)) { std::cout << "T1 happened before T2\n"; } // or if ((t1.year < t2.year) || ((t1.year == t2.year) && ((t1.month < t2.month) || ((t1.month == t2.month) && ((t1.day < t2.day) || ((t1.day == t2.day) && (t1.hour etc..... 

当然,通过使用一侧具有结构而int作为替代的联合,您可以充分利用这两个世界。 显然,这将完全取决于编译器的工作方式,并且您需要测试对象是否放置在正确的位置(但这是了解TDD的最佳位置)。

仅当您的体系结构明确具有一组用于按位操作和访问的指令时。

要看。 如果你使用位字段,那么你让编译器担心如何存储数据(位域几乎完全是实现定义的),这意味着:

  • 它可能会占用超出必要的空间,并且
  • 访问每个成员将有效地完成。

编译器通常会组织结构的布局,以便第二个假设成立,代价是结构的总大小。

编译器可能会在每个成员之间插入填充,以便于访问每个字段。

另一方面,如果您只是将所有内容存储在一个unsigned long (或一组chars)中,那么由您来实现高效访问,但您可以保证布局。 它将占用固定的大小,并且不会有填充。 这意味着复制价值可能会降低成本。 并且它将更加可移植(假设您使用固定大小的int类型而不是unsigned int )。

编译器有时可以在非直观的事物中组合对位域的访问。 我曾经在访问条件表达式中使用的1位条目时反汇编生成的代码(spcc的gcc 3.4.6)。 编译器融合了对位的访问,并与整数进行了比较。 我将尝试重现这个想法(不是在工作,不能访问所涉及的源代码):

 struct bits { int b1:1; int b2:1; int b3:1; ... } x; if(x.b1 && x.b2 && !x.b3) ... if(x.b2 && !x.b2 && x.b3) 

编译成等价的东西(我知道我的例子中的投注者是相反的,但它只是为了简化示例)。

 temp = (x & 7); if( temp == 6) ... if( temp == 5) 

如果你想要使用位域(它们通常比bit-kungfu更具可读性)还有另外一点要考虑,如果你有一些备用位,那么为某些字段保留整个字节会很有用,从而简化了处理器的访问。 对齐的8位字段可以使用移动字节命令加载,并且不需要屏蔽步骤。