我已经明白,最好避免使用_mm_set_epi*
,而是依靠_mm_load_si128
(甚至_mm_loadu_si128
,如果数据不是对齐)。但是,这对性能的影响似乎与我不一致。以下是一个很好的例子。
考虑以下两个利用SSE内在函数的函数:
static uint32_t clmul_load(uint16_t x, uint16_t y)
{
const __m128i c = _mm_clmulepi64_si128(
_mm_load_si128((__m128i const*)(&x)),
_mm_load_si128((__m128i const*)(&y)), 0);
return _mm_extract_epi32(c, 0);
}
static uint32_t clmul_set(uint16_t x, uint16_t y)
{
const __m128i c = _mm_clmulepi64_si128(
_mm_set_epi16(0, 0, 0, 0, 0, 0, 0, x),
_mm_set_epi16(0, 0, 0, 0, 0, 0, 0, y), 0);
return _mm_extract_epi32(c, 0);
}
以下功能对两者的表现进行了基准测试:
template <typename F>
void benchmark(int t, F f)
{
std::mt19937 rng(static_cast<unsigned int>(std::time(0)));
std::uniform_int_distribution<uint32_t> uint_dist10(
0, std::numeric_limits<uint32_t>::max());
std::vector<uint32_t> vec(t);
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < t; ++i)
{
vec[i] = f(uint_dist10(rng), uint_dist10(rng));
}
auto duration = std::chrono::duration_cast<
std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() -
start);
std::cout << (duration.count() / 1000.0) << " seconds.\n";
}
最后,以下主程序进行了一些测试:
int main()
{
const int N = 10000000;
benchmark(N, clmul_load);
benchmark(N, clmul_set);
}
在带有MSVC 2013的i7 Haswell上,典型的输出是
0.208 seconds. // _mm_load_si128
0.129 seconds. // _mm_set_epi16
将GCC与参数-O3 -std=c++11 -march=native
(稍微较旧的硬件)一起使用,典型输出为
0.312 seconds. // _mm_load_si128
0.262 seconds. // _mm_set_epi16
这解释了什么?是否有_mm_set_epi*
优于_mm_load_si128
的情况?还有一些时候我注意到_mm_load_si128
表现得更好,但我无法真正描述这些观察结果。
答案 0 :(得分:5)
你的编译器正在优化&#34;收集&#34; _mm_set_epi16()
来电的行为,因为它确实不需要。从g ++ 4.8(-O3)和gdb:
(gdb) disas clmul_load
Dump of assembler code for function clmul_load(uint16_t, uint16_t):
0x0000000000400b80 <+0>: mov %di,-0xc(%rsp)
0x0000000000400b85 <+5>: mov %si,-0x10(%rsp)
0x0000000000400b8a <+10>: vmovdqu -0xc(%rsp),%xmm0
0x0000000000400b90 <+16>: vmovdqu -0x10(%rsp),%xmm1
0x0000000000400b96 <+22>: vpclmullqlqdq %xmm1,%xmm0,%xmm0
0x0000000000400b9c <+28>: vmovd %xmm0,%eax
0x0000000000400ba0 <+32>: retq
End of assembler dump.
(gdb) disas clmul_set
Dump of assembler code for function clmul_set(uint16_t, uint16_t):
0x0000000000400bb0 <+0>: vpxor %xmm0,%xmm0,%xmm0
0x0000000000400bb4 <+4>: vpxor %xmm1,%xmm1,%xmm1
0x0000000000400bb8 <+8>: vpinsrw $0x0,%edi,%xmm0,%xmm0
0x0000000000400bbd <+13>: vpinsrw $0x0,%esi,%xmm1,%xmm1
0x0000000000400bc2 <+18>: vpclmullqlqdq %xmm1,%xmm0,%xmm0
0x0000000000400bc8 <+24>: vmovd %xmm0,%eax
0x0000000000400bcc <+28>: retq
End of assembler dump.
vpinsrw
(插入字)比clmul_load的未对齐双四字移动快得多,可能是由于内部加载/存储单元能够同时执行较小的读取而不是16B。如果你做了更多的任意负载,显然会消失。
答案 1 :(得分:1)
_mm_set_epi*
的缓慢来自需要将各种变量合并到一个矢量中。您必须检查生成的程序集是否确定,但我的猜测是,由于_mm_set_epi16
调用的大多数参数都是常量(并且为零),因此GCC生成的内容相当短,内在的快速指令集。