我知道最好避免使用
考虑以下两个利用SSE内置函数的函数:
下面的函数对这两种方法进行性能基准测试:
最后,以下的主程序进行了一些测试:
在一台搭载MSVC 2013的i7 Haswell上,典型的输出结果是:
_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_insert_epi16
。类似这样的_mm_insert_epi16(_mm_setzero_si128(),x,0)
- 不确定是否完全正确。 - Z boson