C99中数据结构的效率(可能受字节序影响)

时间:2010-12-25 19:40:30

标签: c performance c99 endianness

我有几个相互关联的问题。基本上,在算法中我实现的一个单词w被定义为四个字节,因此它可以整体包含在uint32_t中。

但是,在算法运行期间,我经常需要访问单词的各个部分。现在,我可以通过两种方式做到这一点:

uint32_t w = 0x11223344;
uint8_t a = (w & 0xff000000) >> 24;
uint8_t b = (w & 0x00ff0000) >> 16;
uint8_t b = (w & 0x0000ff00) >>  8;
uint8_t d = (w & 0x000000ff);

然而,我的一部分认为不是特别有效。我认为更好的方法是使用union表示,如下所示:

typedef union
{
    struct
    {
        uint8_t d;
        uint8_t c;
        uint8_t b;
        uint8_t a;
    };
    uint32_t n;
} word32;

使用此方法我可以指定word32 w = 0x11223344;然后我可以访问各种 我需要的部分(小端的w.a=11)。

然而,在这个阶段我遇到了字节序问题,即在大端系统中我的结构定义不正确所以我需要在传入之前重新排序。

我可以毫不费力地做到这一点。那么,与使用联合的实现相比,我的问题是第一部分(各种按位和转换)是否有效?一般两者有什么区别吗?我应该选择哪种方式使用现代的x86_64处理器?字节序只是一个红鲱鱼吗?

我当然可以检查汇编输出,但我对编译器的了解并不精彩。我本以为联合会更有效率,因为它本质上会转换为内存偏移,如下所示:

mov eax, [r9+8]

编译器是否会意识到上面的位移情况会发生什么?

如果重要,我正在使用C99,特别是我的编译器是clang(llvm)。

提前致谢。

4 个答案:

答案 0 :(得分:2)

如果不能在代码中检查这些操作的实际用途,就很难说出这样的事情:

  • 班次版本可能会这样做 如果碰巧拥有所有你的更好 寄存器中的变量,无论如何,和 然后你做了密集的计算 他们。通常编译器(包括clang)在发布部分单词和类似内容的指令时相对聪明。
  • 联合版本可能是 如果你需要加载,效率更高 来自内存的大部分字节 时间

在任何情况下,我都会将访问操作抽象为一个宏,这样你就可以在有了工作代码的情况下轻松修改它。

根据我的个人喜好,我会选择转换版本,因为它在概念上更简单,只有当我看到生产的汇编程序看起来不太令人满意时,才会转到union

答案 1 :(得分:2)

如果您需要AES,为什么不使用现有的实施?这对于具有AES的硬件支持的现代英特尔处理器尤其有用。

由于存储到转发(STLF)故障,联合技巧可能会减慢速度。如果您将数据写入内存并尽快将其作为不同的数据类型(例如32位对8位)读取,则可能会发生这种情况,具体取决于处理器型号。

答案 2 :(得分:1)

我猜想使用联合可能会更有效率。当然,编译器可能能够优化转换为字节加载,因为它们在编译期间是已知的 - 在这种情况下,两种方案都将产生相同的代码。

另一个选项(也取决于字节顺序)是将字转换为字节数组并直接访问字节。即,类似于以下内容

uint8_t b = ((uint8_t*)w)[n] 

我不确定你会发现真正的现代32/64位处理器有什么不同。

编辑:在两种情况下,clang似乎都会产生相同的代码。

答案 3 :(得分:1)

鉴于使用移位和屏蔽访问位是一种常见操作,我希望编译器能够非常智能,特别是如果你使用恒定的移位计数和掩码。

一个选项是将宏用于bit set / get,这样你就可以在配置时选择最好的策略,如果在特定的平台上,编译器恰好是愚蠢的一面(并且明智地选择宏的名称也可以使代码更清晰,自我解释。)