2012-03-22 86 views
11

我目前正在编写一个C++模板表达式库,并将一些实例与汇编级别的手写代码进行比较。手写功能如下:表达式模板与手写代码

spinor multiply(vector const& a, vector const& b) 
{ 
     spinor result = { 
       a.at<1>() * b.at<1>() - a.at<2>() * b.at<2>() 
          - a.at<4>() * b.at<4>() - a.at<8>() * b.at<8>(), 
       a.at<1>() * b.at<2>() - a.at<2>() * b.at<1>(), 
       a.at<1>() * b.at<4>() - a.at<4>() * b.at<1>(), 
       a.at<1>() * b.at<8>() - a.at<8>() * b.at<1>(), 
       a.at<2>() * b.at<4>() - a.at<4>() * b.at<2>(), 
       a.at<2>() * b.at<8>() - a.at<8>() * b.at<2>(), 
       a.at<4>() * b.at<8>() - a.at<8>() * b.at<4>() 
     }; 

     return result; 
} 

vector类只是在四个双打的包装,其可以通过使用at<index>()成员函数被读取。由于设计决定,四个组件的索引是1, 2, 4, 8,可通过at<index>()而不是通常的0, 1, 2, 3进行访问。

该函数的用途是返回两个向量相乘的结果(在Minkowski空间中)。如果您熟悉几何代数,您将看到点积(result的第一个组成部分,在ab的交换对称下)和楔形积(其余部分,在ab的交换下反对称)。如果您对几何代数不熟悉,只需将此函数作为乘法向量的处方。

如果我编译功能上面GCC 4.7,并期待在由objdump -SC a.out此给出的拆解给我下面的输出:

400bc0: movsd 0x8(%rsi),%xmm6 
400bc5: mov %rdi,%rax 
400bc8: movsd (%rsi),%xmm8 
400bcd: movsd 0x8(%rdx),%xmm5 
400bd2: movapd %xmm6,%xmm9 
400bd7: movsd (%rdx),%xmm7 
400bdb: movapd %xmm8,%xmm0 
400be0: mulsd %xmm5,%xmm9 
400be5: movsd 0x10(%rsi),%xmm4 
400bea: mulsd %xmm7,%xmm0 
400bee: movsd 0x10(%rdx),%xmm1 
400bf3: movsd 0x18(%rdx),%xmm3 
400bf8: movsd 0x18(%rsi),%xmm2 
400bfd: subsd %xmm9,%xmm0 
400c02: movapd %xmm4,%xmm9 
400c07: mulsd %xmm1,%xmm9 
400c0c: subsd %xmm9,%xmm0 
400c11: movapd %xmm3,%xmm9 
400c16: mulsd %xmm2,%xmm9 
400c1b: subsd %xmm9,%xmm0 
400c20: movapd %xmm6,%xmm9 
400c25: mulsd %xmm7,%xmm9 
400c2a: movsd %xmm0,(%rdi) 
400c2e: movapd %xmm5,%xmm0 
400c32: mulsd %xmm8,%xmm0 
400c37: subsd %xmm9,%xmm0 
400c3c: movapd %xmm4,%xmm9 
400c41: mulsd %xmm7,%xmm9 
400c46: mulsd %xmm2,%xmm7 
400c4a: movsd %xmm0,0x8(%rdi) 
400c4f: movapd %xmm1,%xmm0 
400c53: mulsd %xmm8,%xmm0 
400c58: mulsd %xmm3,%xmm8 
400c5d: subsd %xmm9,%xmm0 
400c62: subsd %xmm7,%xmm8 
400c67: movapd %xmm4,%xmm7 
400c6b: mulsd %xmm5,%xmm7 
400c6f: movsd %xmm0,0x10(%rdi) 
400c74: mulsd %xmm2,%xmm5 
400c78: movapd %xmm1,%xmm0 
400c7c: mulsd %xmm6,%xmm0 
400c80: movsd %xmm8,0x18(%rdi) 
400c86: mulsd %xmm3,%xmm6 
400c8a: mulsd %xmm2,%xmm1 
400c8e: mulsd %xmm4,%xmm3 
400c92: subsd %xmm7,%xmm0 
400c96: subsd %xmm5,%xmm6 
400c9a: subsd %xmm1,%xmm3 
400c9e: movsd %xmm0,0x20(%rdi) 
400ca3: movsd %xmm6,0x28(%rdi) 
400ca8: movsd %xmm3,0x30(%rdi) 
400cad: retq 
400cae: nop 
400caf: nop 

这看起来很不错,我 - 第一的部件(%rsi)和第二个(%rdx)向量只能访问一次,实际计算仅在寄存器中完成。最后,结果写在%rdi的地址上。由于这是第一个参数寄存器,我认为这里使用了返回值优化。

比较这与函数的表达式模板版本上面下面的清单:

400cb0: mov (%rsi),%rdx 
400cb3: mov 0x8(%rsi),%rax 
400cb7: movsd 0x1f1(%rip),%xmm4  # 400eb0 <_IO_stdin_used+0x10> 
400cbe: 
400cbf: movsd 0x10(%rdx),%xmm3 
400cc4: movsd 0x18(%rdx),%xmm0 
400cc9: mulsd 0x10(%rax),%xmm3 
400cce: xorpd %xmm4,%xmm0 
400cd2: mulsd 0x18(%rax),%xmm0 
400cd7: movsd 0x8(%rdx),%xmm2 
400cdc: movsd (%rdx),%xmm1 
400ce0: mulsd 0x8(%rax),%xmm2 
400ce5: mulsd (%rax),%xmm1 
400ce9: subsd %xmm3,%xmm0 
400ced: subsd %xmm2,%xmm0 
400cf1: addsd %xmm0,%xmm1 
400cf5: movsd %xmm1,(%rdi) 
400cf9: movsd (%rdx),%xmm0 
400cfd: movsd 0x8(%rdx),%xmm1 
400d02: mulsd 0x8(%rax),%xmm0 
400d07: mulsd (%rax),%xmm1 
400d0b: subsd %xmm1,%xmm0 
400d0f: movsd %xmm0,0x8(%rdi) 
400d14: movsd (%rdx),%xmm0 
400d18: movsd 0x10(%rdx),%xmm1 
400d1d: mulsd 0x10(%rax),%xmm0 
400d22: mulsd (%rax),%xmm1 
400d26: subsd %xmm1,%xmm0 
400d2a: movsd %xmm0,0x10(%rdi) 
400d2f: movsd 0x8(%rdx),%xmm0 
400d34: movsd 0x10(%rdx),%xmm1 
400d39: mulsd 0x10(%rax),%xmm0 
400d3e: mulsd 0x8(%rax),%xmm1 
400d43: subsd %xmm1,%xmm0 
400d47: movsd %xmm0,0x18(%rdi) 
400d4c: movsd (%rdx),%xmm0 
400d50: movsd 0x18(%rdx),%xmm1 
400d55: mulsd 0x18(%rax),%xmm0 
400d5a: mulsd (%rax),%xmm1 
400d5e: subsd %xmm1,%xmm0 
400d62: movsd %xmm0,0x20(%rdi) 
400d67: movsd 0x8(%rdx),%xmm0 
400d6c: movsd 0x18(%rdx),%xmm1 
400d71: mulsd 0x18(%rax),%xmm0 
400d76: mulsd 0x8(%rax),%xmm1 
400d7b: subsd %xmm1,%xmm0 
400d7f: movsd %xmm0,0x28(%rdi) 
400d84: movsd 0x10(%rdx),%xmm0 
400d89: movsd 0x18(%rdx),%xmm1 
400d8e: mulsd 0x18(%rax),%xmm0 
400d93: mulsd 0x10(%rax),%xmm1 
400d98: subsd %xmm1,%xmm0 
400d9c: movsd %xmm0,0x30(%rdi) 
400da1: retq 

这个函数的签名是

spinor<product<vector, vector>>(product<vector, vector> const&) 

我希望你们相信我,这两个版本提供相同结果。前两行提取第一个和第二个向量,它们作为参考存储在product中。我想知道以下几点:

  • movsd 0x1f1(%rip),%xmm4xorpd %xmm4,%xmm0组合是做什么的?我已经发现这被称为“RIP相对寻址”,参见http://www.x86-64.org/documentation/assembly.html
  • 为什么GCC不使用更多的寄存器,例如,缓存0x10(%rax)这是读取四次?

我还通过产生亿个随机矢量并考虑两种功能所需的时间基准两种功能:

ET: 7.5 sec 
HW: 6.8 sec 

手写函数是快约10%。有没有人有表达模板的经验,并知道如何使他们更接近他们的手写对应?

+0

与你的问题没有任何关系,但即使有范围规则,我也会试着不要将新的类命名为与某些标准容器类相同的名称。如果你使用'使用命名空间标准;'事情可能会变得非常混乱。 – 2012-03-22 11:40:56

+2

@这将是艰难的没有实际的ET代码。小心分享? – sehe 2012-03-22 12:03:29

+1

@JoachimPileborg:如果你使用'using namespace std;'我不在乎你是否遇到问题(你应该更具体)......但我同意关于试图避免名称冲突的一般想法。 – 2012-03-22 12:22:29

回答

3

如果我们确实知道地址0x400eb0的内容,但我怀疑它是0x8000 0000 0000 0000 8000 0000 0000 0000或类似的(可能带有前导0,因为代码没有被矢量化),写成128位int,这将是清楚的。

在这种情况下,xorpd确实会更改第二个操作数的符号。

为什么寄存器读取没有被缓存 - 最好在gcc-help邮件列表中提问。编译器可能无法证明这两个向量或中间结果不是别名。但是对于一般的观点,编译器并不总是完美地进行优化,但只有优于所有程序员的90%(或99%)(如果他们试图编写程序集),有时(很少)他们产生的很慢码。

但是您的方法非常好 - 如果您想优化,基准测试和寻找生成的目标代码是正确的。 PS:通过使用向量指令(mulpd而不是mulsd),它可以加速使用矢量指令,该指令一次倍增两个或四个双打),也称为SSE或AVX。但是需要一些指令来将值移动到寄存器中的正确位置,所以增益总是比两或四倍慢。

+0

我也不知道在这个地址,它指向我的代码的末尾(见上面由objdump生成的注释)。这里是一个完整的打印输出: – cschwan 2012-03-22 14:07:43

+0

http://www.students.uni-mainz.de/cschwan/asm.out(旧版本,但情况仍然存在,见行373) – cschwan 2012-03-22 14:14:17

+0

我也想过使用SSE,但我认为这很难将表达式模板与SSE内在函数结合起来,特别是因为计算可能包含符号并且可能非常不同(比较'multiply'中的第一个和第二个组件)。 – cschwan 2012-03-22 14:47:54