【问题标题】:transpose for 8 registers of 16-bit elements on SSE2/SSSE3在 SSE2/SSSE3 上转置 8 个 16 位元素的寄存器
【发布时间】:2011-01-31 20:01:26
【问题描述】:

(我是 SSE/asm 的新手,如果这很明显或多余,我深表歉意)

有没有比执行 24 unpck[lh]ps 和 8/16+ shuffle 和使用 8 个额外寄存器更好的方法来转置 8 个包含 16 位值的 SSE 寄存器? (注意最多使用 SSSE 3 指令,Intel Merom,也就是缺少 SSE4 中的 BLEND*。)

假设您有寄存器 v[0-7] 并使用 t0-t7 作为辅助寄存器。在伪内在代码中:

/* Phase 1: process lower parts of the registers */
/* Level 1: work first part of the vectors */
/*   v[0]  A0 A1 A2 A3 A4 A5 A6 A7
**   v[1]  B0 B1 B2 B3 B4 B5 B6 B7
**   v[2]  C0 C1 C2 C3 C4 C5 C6 C7
**   v[3]  D0 D1 D2 D3 D4 D5 D6 D7
**   v[4]  E0 E1 E2 E3 E4 E5 E6 E7
**   v[5]  F0 F1 F2 F3 F4 F5 F6 F7
**   v[6]  G0 G1 G2 G3 G4 G5 G6 G7
**   v[7]  H0 H1 H2 H3 H4 H5 H6 H7 */
t0 = unpcklps (v[0], v[1]); /* Extract first half interleaving */
t1 = unpcklps (v[2], v[3]); /* Extract first half interleaving */
t2 = unpcklps (v[4], v[5]); /* Extract first half interleaving */
t3 = unpcklps (v[6], v[7]); /* Extract first half interleaving */
t0 = pshufhw (t0, 0xD8); /* Flip middle 2 high */
t0 = pshuflw (t0, 0xD8); /* Flip middle 2 low */
t1 = pshufhw (t1, 0xD8); /* Flip middle 2 high */
t1 = pshuflw (t1, 0xD8); /* Flip middle 2 low */
t2 = pshufhw (t2, 0xD8); /* Flip middle 2 high */
t2 = pshuflw (t2, 0xD8); /* Flip middle 2 low */
t3 = pshufhw (t3, 0xD8); /* Flip middle 2 high */
t3 = pshuflw (t3, 0xD8); /* Flip middle 2 low */
/*   t0   A0 B0 A1 B1 A2 B2 A3 B3  (A B - 0 1 2 3)
**   t1   C0 D0 C1 D1 C2 D2 C3 D3  (C D - 0 1 2 3)
**   t2   E0 F0 E1 F1 E2 F2 E3 F3  (E F - 0 1 2 3)
**   t3   G0 H0 G1 H1 G2 H2 G3 H3  (G H - 0 1 2 3) */
/* L2 */
t4 = unpcklps (t0, t1);
t5 = unpcklps (t2, t3);
t6 = unpckhps (t0, t1);
t7 = unpckhps (t2, t3);
/*   t4   A0 B0 C0 D0 A1 B1 C1 D1 (A B C D - 0 1)
**   t5   E0 F0 G0 H0 E1 F1 G1 H1 (E F G H - 0 1)
**   t6   A2 B2 C2 D2 A3 B3 C3 D3 (A B C D - 2 3)
**   t7   E2 F2 G2 H2 E3 F3 G3 H3 (E F G H - 2 3) */
/* Phase 2: same with higher parts of the registers */
/*   A    A0 A1 A2 A3 A4 A5 A6 A7
**   B    B0 B1 B2 B3 B4 B5 B6 B7
**   C    C0 C1 C2 C3 C4 C5 C6 C7
**   D    D0 D1 D2 D3 D4 D5 D6 D7
**   E    E0 E1 E2 E3 E4 E5 E6 E7
**   F    F0 F1 F2 F3 F4 F5 F6 F7
**   G    G0 G1 G2 G3 G4 G5 G6 G7
**   H    H0 H1 H2 H3 H4 H5 H6 H7 */
t0 = unpckhps (v[0], v[1]);
t0 = pshufhw (t0, 0xD8); /* Flip middle 2 high */
t0 = pshuflw (t0, 0xD8); /* Flip middle 2 low */
t1 = unpckhps (v[2], v[3]);
t1 = pshufhw (t1, 0xD8); /* Flip middle 2 high */
t1 = pshuflw (t1, 0xD8); /* Flip middle 2 low */
t2 = unpckhps (v[4], v[5]);
t2 = pshufhw (t2, 0xD8); /* Flip middle 2 high */
t2 = pshuflw (t2, 0xD8); /* Flip middle 2 low */
t3 = unpckhps (v[6], v[7]);
t3 = pshufhw (t3, 0xD8); /* Flip middle 2 high */
t3 = pshuflw (t3, 0xD8); /* Flip middle 2 low */
/*   t0   A4 B4 A5 B5 A6 B6 A7 B7  (A B - 4 5 6 7)
**   t1   C4 D4 C5 D5 C6 D6 C7 D7  (C D - 4 5 6 7)
**   t2   E4 F4 E5 F5 E6 F6 E7 F7  (E F - 4 5 6 7)
**   t3   G4 H4 G5 H5 G6 H6 G7 H7  (G H - 4 5 6 7) */
/* Back to first part, v[0-3] can be re-written now */
/* L3 */
v[0] = unpcklpd (t4, t5);
v[1] = unpckhpd (t4, t5);
v[2] = unpcklpd (t6, t7);
v[3] = unpckhpd (t6, t7);
/* v[0] = A0 B0 C0 D0 E0 F0 G0 H0
** v[1] = A1 B1 C1 D1 E1 F1 G1 H1
** v[2] = A2 B2 C2 D2 E2 F2 G2 H2
** v[3] = A3 B3 C3 D3 E3 F3 G3 H3 */
/* Back to second part, t[4-7] can be re-written now... */
/* L2 */
t4 = unpcklps (t0, t1);
t5 = unpcklps (t2, t3);
t6 = unpckhps (t0, t1);
t7 = unpckhps (t2, t3);
/*   t4   A4 B4 C4 D4 A5 B5 C5 D5 (A B C D - 4 5)
**   t5   E4 F4 G4 H4 E5 F5 G5 H5 (E F G H - 4 5)
**   t6   A6 B6 C6 D6 A7 B7 C7 D7 (A B C D - 6 7)
**   t7   E6 F6 G6 H6 E7 F7 G7 H7 (E F G H - 6 7) */
/* L3 */
v[4] = unpcklpd (t4, t5);
v[5] = unpckhpd (t4, t5);
v[6] = unpcklpd (t6, t7);
v[7] = unpckhpd (t6, t7);
/* v[4] = A4 B4 C4 D4 E4 F4 G4 H4
** v[5] = A5 B5 C5 D5 E5 F5 G5 H5
** v[6] = A6 B6 C6 D6 E6 F6 G6 H6
** v[7] = A7 B7 C7 D7 E7 F7 G7 H7 */

每个 unpck* 需要 3 个周期的延迟,或 2 个周期用于互惠吞吐量(由 Agner 报告)。这会扼杀使用 SSE(在此代码上)带来的大部分性能提升,因为这种寄存器舞蹈几乎每个元素需要一个周期.我试图理解 x86 转置的 x264 的 asm 文件,但未能理解宏。

谢谢!

【问题讨论】:

    标签: assembly matrix x86 sse simd


    【解决方案1】:

    是的,总共 24 条指令即可完成:

    8 x _mm_unpacklo_epi16/_mm_unpackhi_epi16 (PUNPCKLWD/PUNPCKHWD)
    8 x _mm_unpacklo_epi32/_mm_unpackhi_epi32 (PUNPCKLDQ/PUNPCKHDQ)
    8 x _mm_unpacklo_epi64/_mm_unpackhi_epi64 (PUNPCKLQDQ/PUNPCKHQDQ)
    

    如果您需要更多详细信息,请告诉我,但这很明显。

    【讨论】:

    • 不错,伙计!有没有机会你能指出我在哪里可以找到更多关于 SSE 的基本转换?
    • @aleccolocco:不幸的是,没有很多关于 SSE 的好材料,至少对于更高级的主题是这样。我建议查看 AltiVec 资源(例如 developer.apple.com)——很多 AltiVec 技术很容易转化为 SSE。
    • 好消息:我设法做到了。坏消息:测量 100 万个元素时性能仅提升 5%。不过谢谢,我学到了一些很酷的 SSE 技巧!
    • @aleccolocco:如果你只是在做一个内存到内存的转置而不做其他事情,那么你的性能很可能会受到内存带宽等的限制——通常你会得到更好的整体性能如果您可以将转置与其他操作结合起来。另请注意,不同 CPU 系列之间的 SSE 性能差异巨大:例如之前 Core 2 Duo = abysmal, Core 2 Duo = good, Core i7 = rocks !
    • @rwong:这实际上只适用于旧 CPU(在 Core 2 之前)——从 Core 2 开始,英特尔开始采用完整的 128 位 SSE 实现
    【解决方案2】:

    我的想法来自这个http://www.randombit.net/bitbashing/programming/integer_matrix_transpose_in_sse2.html

    我会将一个 8x8 分割成四个 4x4 而不是提到的技巧。 最后交换block(0,1)和block(1,0)

    但是,我仍然不明白 Paul R 的伎俩。 保罗你能再给我一些点击吗?

    【讨论】:

    【解决方案3】:

    我必须自己做,所以这是我的最终结果。请注意,我使用的加载和存储指令适用于 16 字节对齐的数组,它们是使用声明的 m128i* 数组 = (__m128i*) _mm_malloc(N*sizeof(uint16_t), 16); 要么 int16_t 数组[N]__attribute((aligned(16)));

    __m128i *p_input  = (__m128i*)array;
    __m128i *p_output = (__m128i*)array;
    __m128i a = _mm_load_si128(p_input++);
    __m128i b = _mm_load_si128(p_input++);
    __m128i c = _mm_load_si128(p_input++);
    __m128i d = _mm_load_si128(p_input++);
    __m128i e = _mm_load_si128(p_input++);
    __m128i f = _mm_load_si128(p_input++);
    __m128i g = _mm_load_si128(p_input++);
    __m128i h = _mm_load_si128(p_input);
    
    __m128i a03b03 = _mm_unpacklo_epi16(a, b);
    __m128i c03d03 = _mm_unpacklo_epi16(c, d);
    __m128i e03f03 = _mm_unpacklo_epi16(e, f);
    __m128i g03h03 = _mm_unpacklo_epi16(g, h);
    __m128i a47b47 = _mm_unpackhi_epi16(a, b);
    __m128i c47d47 = _mm_unpackhi_epi16(c, d);
    __m128i e47f47 = _mm_unpackhi_epi16(e, f);
    __m128i g47h47 = _mm_unpackhi_epi16(g, h);
    
    __m128i a01b01c01d01 = _mm_unpacklo_epi32(a03b03, c03d03);
    __m128i a23b23c23d23 = _mm_unpackhi_epi32(a03b03, c03d03);
    __m128i e01f01g01h01 = _mm_unpacklo_epi32(e03f03, g03h03);
    __m128i e23f23g23h23 = _mm_unpackhi_epi32(e03f03, g03h03);
    __m128i a45b45c45d45 = _mm_unpacklo_epi32(a47b47, c47d47);
    __m128i a67b67c67d67 = _mm_unpackhi_epi32(a47b47, c47d47);
    __m128i e45f45g45h45 = _mm_unpacklo_epi32(e47f47, g47h47);
    __m128i e67f67g67h67 = _mm_unpackhi_epi32(e47f47, g47h47);
    
    __m128i a0b0c0d0e0f0g0h0 = _mm_unpacklo_epi64(a01b01c01d01, e01f01g01h01);
    __m128i a1b1c1d1e1f1g1h1 = _mm_unpackhi_epi64(a01b01c01d01, e01f01g01h01);
    __m128i a2b2c2d2e2f2g2h2 = _mm_unpacklo_epi64(a23b23c23d23, e23f23g23h23);
    __m128i a3b3c3d3e3f3g3h3 = _mm_unpackhi_epi64(a23b23c23d23, e23f23g23h23);
    __m128i a4b4c4d4e4f4g4h4 = _mm_unpacklo_epi64(a45b45c45d45, e45f45g45h45);
    __m128i a5b5c5d5e5f5g5h5 = _mm_unpackhi_epi64(a45b45c45d45, e45f45g45h45);
    __m128i a6b6c6d6e6f6g6h6 = _mm_unpacklo_epi64(a67b67c67d67, e67f67g67h67);
    __m128i a7b7c7d7e7f7g7h7 = _mm_unpackhi_epi64(a67b67c67d67, e67f67g67h67);
    
    _mm_store_si128(p_output++, a0b0c0d0e0f0g0h0);
    _mm_store_si128(p_output++, a1b1c1d1e1f1g1h1);
    _mm_store_si128(p_output++, a2b2c2d2e2f2g2h2);
    _mm_store_si128(p_output++, a3b3c3d3e3f3g3h3);
    _mm_store_si128(p_output++, a4b4c4d4e4f4g4h4);
    _mm_store_si128(p_output++, a5b5c5d5e5f5g5h5);
    _mm_store_si128(p_output++, a6b6c6d6e6f6g6h6);
    _mm_store_si128(p_output, a7b7c7d7e7f7g7h7);
    

    【讨论】:

      猜你喜欢
      • 2017-07-12
      • 2012-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-15
      • 2012-04-15
      • 2013-05-14
      相关资源
      最近更新 更多