2012-03-22 90 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