我的任務是將一些 c 代碼轉換為 asm,我想知道我的想法是否有意義。首先,我會將整數轉換為浮點數。我想將陣列資料獲取到 sse 暫存器,但這是個問題,因為我只想要 3 個而不是 4 個整數,有沒有辦法克服這個問題?然后我會使用 CVTDQ2PS 將這些整數轉換為浮點數,并將這些數字保存在記憶體中。對于像 0.393 這樣的 const 數字,我會制作 3 個浮點向量,然后我會做同樣的操作 3 次,所以我只會考慮 sepiaRed。為此,我會將轉換后的整數放入 sse 暫存器中,然后將這些數字相乘,從而得到 xmm0 暫存器中的結果。現在我怎樣才能把它們加在一起?
我想我的兩個問題是:如何從陣列中獲取 3 個專案到 sse 暫存器,這樣我就可以避免任何問題。然后如何將 xmm0 暫存器中的三個數字相加。
tmpGreen = (float)pixels[i 1];
tmpRed = (float)pixels[i 2];
tmpBlue = (float)pixels[i];
sepiaRed = (int)(0.393 * tmpRed 0.769 * tmpGreen 0.189 * tmpBlue); //red
sepiaGreen = (int)(0.349 * tmpRed 0.686 * tmpGreen 0.168 * tmpBlue); //green
sepiaBlue = (int)(0.272 * tmpRed 0.534 * tmpGreen 0.131 * tmpBlue); //blue
uj5u.com熱心網友回復:
如果您關心速度,則應避免使用浮點域并僅使用定點(并使用 8/16 位算術)。
即使(雙精度)浮點因子在基數 10 中很短,但在基數 2 中它們并沒有那么短:
0.393 = 3.93000000000000015987211554602E-1 == 0x3FD926E978D4FDF4
0.168 = 1.68000000000000010436096431476E-1 == 0x3FC5810624DD2F1B
etc.
鑒于原始整數 r,g,b 資料被限制在 0..255 范圍內,因子中的最右邊的位沒有貢獻。因此,我們也可能只是截斷或舍入二進制表示。
如果 7 位的系數精度就足夠了,我們可以得出系數矩陣
50 98 24 == 0x32 0x62 0x18
45 88 22 == 0x2d 0x58 0x16
35 68 17 == 0x23 0x44 0x11
7 位,因為在 SSE 中計算小點積的最快方法是_mm_maddubs_epi16,它可以將 uint8_t RGB 與 8 位有符號(或 7 位無符號)系數相乘。
然后我們需要適當地安排輸入和系數矩陣。
選項 1:交錯
R0G0B0R1G1B1R2G2B2R3G3B3R4G4B4R5G5B5R6G6B6...
選項 2:平面:
R0R1R2R3... G0G1G2G3... B0B1B2B3....
無論哪種方式,目標都是將資料重新洗牌
xmm0 = R0G0R1G1R2G2R3G3R4G4R5G5R6G6R7G7
xmm1 = B0xxB1xxB2xxB3xxB4xxB5xxB6xxB7xx
rg0 = 326232623262...
b0. = 180018001800...
r_new_0 = _mm_maddubs_epi16(xmm0, rg0);
g_new_0 = _mm_maddubs_epi16(xmm0, rg1);
b_new_0 = _mm_maddubs_epi16(xmm0, rg2);
r_new_1 = _mm_maddubs_epi16(xmm1, b0);
g_new_1 = _mm_maddubs_epi16(xmm1, b1);
b_new_1 = _mm_maddubs_epi16(xmm1, b2);
r_new_0 = _mm_add_epi16(r_new_0, r_new_1);
g_new_0 = _mm_add_epi16(g_new_0, g_new_1);
b_new_0 = _mm_add_epi16(b_new_0, b_new_1);
然后我們需要右移 7 并轉換為 uint8_t。這種轉換需要飽和,因為每列的系數總和都大于 128。
r_new_0 = _mm_srli_epi16(r_new_0, 7);
r_new_0 = _mm_packus_epi16(r_new_0, r_new_0);
... and same for g_new_0, b_new_0
這最后一步顯示了一個非常小的低效率,因為一半的暫存器容量丟失了;消耗 24 個位元組的輸入,我們產生了 8 8 8 個輸出。
無論如何開始使用 16 16 16 輸入位元組可能會更好,這會導致 12 次乘法,第一次是在加法時及時完成的??。
如果您絕對堅持使用浮點數,我會為交錯資料使用類似的東西(如您的情況):
auto data_ptr = reinterpret_cast<const uint32_t *>(pixels);
__m128i rgbi = _mm_cvtsi32_si128(*data_ptr);
#if SSE4_ENABLED
rgbi = _mm_cvtepu8_epi32(rgbi);
#elif SSE3_ENABLED
auto const k0123 = _mm_set_epi32(-1,2,1,0);
rgbi = _mm_shuffle_epi32(rgbi, k0123);
#else
rgbi = _mm_unpacklo_epi8(rgbi, _mm_setzero_si128());
rgbi = _mm_unpacklo_epi16(rgbi, _mm_setzero_si128());
#endif
// having expanded 3 x uint8_t -> 3 x int32_t garbage
auto rgb_f = _mm_cvtepi32_ps(rgbi);
// shuffle the rgb to gbr and brg
auto gbr_f = _mm_shuffle_ps(rgb_f, rgb_f, 0b00001001);
auto brg_f = _mm_shuffle_ps(rgb_f, rgb_f, 0b00010010);
// now we multiply the permuted rgb vectors with
// permuted coefficients
rgb_f = _mm_mul_ps(rgb_f, coeffs0);
gbr_f = _mm_mul_ps(gbr_f, coeffs1);
brg_f = _mm_mul_ps(brg_f, coeffs2);
// sum up vertically
// for rgb_f[0] = R, rgb_f[1] = G, rgb_f[2] = B
rgb_f = _mm_add_ps(rgb_f, gbr_f);
rgb_f = _mm_add_ps(rgb_f, brg_f);
rgbi = _mm_cvtepi32_ps(rgb_f); // back to 32-bit integer
然后在飽和時轉換回 uint8_t —— SSE4.1 has_mm_packus_epi32和 SSE2 has _mm_packus_epi16,可以代替使用,因為 的預期范圍rgbi正好在 0 到 345 之間,因此適合int16_t. 但packus_epi16不幸的是,使用會在連續的輸出通道之間留下零,沒有_mm_shuffle_epi8它很難重新洗牌,只有從 SSSE3 開始才可用。
無論如何,我們看到,預先安排有助于消除水平累積,但我們也看到我們失去了大約 25% 的計算能力,因為不使用通道 #3 并花費時間進行洗牌。輸入的布局應該修改...
uj5u.com熱心網友回復:
您不能輕松地將 3 個數字水平相加;進行水平 SSE 向量求和(或其他縮減)的最快方法
您可以有效地做的是并行映射 4 個像素,使用 4 個紅色、4 個綠色和 4 個藍色的向量。(您希望從平面而非交錯的像素資料中加載。陣列的結構,而不是結構的陣列。)
你也許能夠得到一些好處的同時做一個像素,不過,如果你只加載4個整數與movdqu和使用的乘數0.0為后高的元素cvtdq2ps。然后您可以對 4 個元素進行正常的水平總和,而不必對其進行調整。(嗯,雖然做 3 會讓你在第一次添加的同時進行第二次洗牌,而不是在之后。)
使用 SIMD 效率低下會失去一些好處;請參閱https://stackoverflow.com/tags/sse/info 中的指南,尤其是https://deplinenoise.wordpress.com/2015/03/06/slides-simd-at-insomniac-games-gdc-2015/回復:如何人們經常嘗試使用一個 SIMD 向量來保存一個 x,y,z 幾何向量,然后發現 SIMD 并沒有多大幫助。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/408733.html
標籤:
