工会比现代编译器的转变更有效吗?

时间:2011-05-25 18:00:09

标签: c performance compiler-optimization unions shift

考虑简单的代码:

UINT64 result;
UINT32 high, low;
...
result = ((UINT64)high << 32) | (UINT64)low;

现代编译器是否会将其转换为真正的高位移位,或者将其优化为简单的副本到正确的位置?

如果没有,那么使用联合似乎比大多数人似乎使用的转变更有效。但是,让编译器优化它是理想的解决方案。

我想知道当他们需要额外的一点性能时,我应该如何建议他们。

4 个答案:

答案 0 :(得分:4)

现代编译器比你想象的更聪明;-)(所以是的,我认为你可以期待任何体面的编译器发生桶式转换。)

无论如何,我会使用具有更接近你实际尝试的语义的选项。

答案 1 :(得分:4)

如果这应该是平台无关的,那么唯一的选择就是在这里使用轮班。

使用union { r64; struct{low;high}},您无法确定哪些低/高字段将映射到。考虑一下endianess。

现代编译器非常善于处理这种转变。

答案 2 :(得分:4)

我写了以下(希望是有效的)测试:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

void func(uint64_t x);

int main(int argc, char **argv)
{
#ifdef UNION
  union {
    uint64_t full;
    struct {
      uint32_t low;
      uint32_t high;
    } p;
  } result;
  #define value result.full
#else
  uint64_t result;
  #define value result
#endif
  uint32_t high, low;

  if (argc < 3) return 0;

  high = atoi(argv[1]);
  low = atoi(argv[2]);

#ifdef UNION
  result.p.high = high;
  result.p.low = low;
#else
  result = ((uint64_t) high << 32) | low;
#endif

  // printf("%08x%08x\n", (uint32_t) (value >> 32), (uint32_t) (value & 0xffffffff));
  func(value);

  return 0;
}

运行未优化的gcc -s输出的差异:

<   mov -4(%rbp), %eax
<   movq    %rax, %rdx
<   salq    $32, %rdx
<   mov -8(%rbp), %eax
<   orq %rdx, %rax
<   movq    %rax, -16(%rbp)
---
>   movl    -4(%rbp), %eax
>   movl    %eax, -12(%rbp)
>   movl    -8(%rbp), %eax
>   movl    %eax, -16(%rbp)

我不知道集会,所以我很难对其进行分析。然而,看起来正如预期的那样在非联盟(顶级)版本上发生了一些转变。

但启用优化-O2后,输出结果相同。因此生成了相同的代码,两种方式都具有相同的性能。

(Linux / AMD64上的gcc 4.5.2版)

带或不带union的优化-O2代码的部分输出:

    movq    8(%rsi), %rdi
    movl    $10, %edx
    xorl    %esi, %esi
    call    strtol

    movq    16(%rbx), %rdi
    movq    %rax, %rbp
    movl    $10, %edx
    xorl    %esi, %esi
    call    strtol

    movq    %rbp, %rdi
    mov     %eax, %eax
    salq    $32, %rdi
    orq     %rax, %rdi
    call    func

该片段在if行生成的跳转后立即开始。

答案 3 :(得分:2)

编辑:此回复基于OP代码的早期版本,没有投射

此代码

result = (high << 32) | low;

实际上会有未定义的结果...因为使用high你将32位值移动32位(值的宽度),结果将是未定义的并且将会取决于编译器和OS平台如何决定处理这种转变。然后,未定义的移位的结果将与low一起使用,这也将是未定义的,因为您对定义的值进行了或未定义的值,因此最终结果很可能不是一个像你想要的64位值。例如,gcc -s在OSX 10.6上发出的代码如下所示:

movl    -4(%rbp), %eax      //retrieving the value of "high"
movl    $32, %ecx          
shal    %cl, %eax           //performing the 32-bit shift on "high"
orl    -8(%rbp), %eax       //OR'ing the value of "low" to the shift op result

因此,您可以看到移位仅发生在具有32位汇编命令的32位寄存器中的32位值...结果最终与high | low完全相同完全没有任何转移,因为在这种情况下,shal $32, %eax只返回EAX中最初的值。你没有得到64位的结果。

为了避免这种情况,请将high投射到uint64_t,如:

result = ((uint64_t)high << 32) | low;