gpt4 book ai didi

c++ - 为什么 _mm_set_epi16 有时比 _mm_load_si128 快?

转载 作者:太空狗 更新时间:2023-10-29 20:25:31 25 4
gpt4 key购买 nike

我知道最好避免 _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

使用带有参数 -O3 -std=c++11 -march=native 的 GCC(硬件稍旧),典型的输出是

0.312 seconds.  // _mm_load_si128
0.262 seconds. // _mm_set_epi16

这是怎么解释的?实际上是否存在 _mm_set_epi* 优于 _mm_load_si128 的情况?有时我注意到 _mm_load_si128 表现更好,但我无法真正描述这些观察结果的特征。

最佳答案

您的编译器正在优化 _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 的。如果您进行更多的任意加载,这显然会消失。

关于c++ - 为什么 _mm_set_epi16 有时比 _mm_load_si128 快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23715728/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com