使用 SIMD 内部函数时如何将依赖于输入的热数据保存在寄存器中

How to keep input-dependent hot data in registers when using SIMD intrinsics

我正在尝试使用英特尔 SIMD 内在函数来加速查询-回答程序。假设 query_cnt 依赖于输入但总是小于 SIMD 寄存器计数(即有足够的 SIMD 寄存器来保存它们)。由于查询是我的应用程序中的热数据,而不是每次需要时都加载它们,我可以先加载它们并始终将它们保存在寄存器中吗?

假设查询是float类型,支持AVX256。现在我必须使用类似的东西:

std::vector<__m256> vec_queries(query_cnt / 8);
for (int i = 0; i < query_cnt / 8; ++i) {
    vec_queries[i] = _mm256_loadu_ps((float const *)(curr_query_ptr)); 
    curr_query_ptr += 8;
}

我知道这不是一个好的做法,因为有潜在的 load/store 开销,但至少有一点机会可以优化 vec_queries[i] 以便它们可以保存在寄存器中,但我仍然认为这不是一个好方法。

有更好的主意吗?

从您发布的代码示例来看,您似乎只是在执行可变长度的 memcpy。根据编译器的作用和周围的代码,您可能会从实际调用 memcpy 中获得更好的结果。例如对于大小为 16B 倍数的对齐副本,矢量循环和 rep movsb 之间的盈亏平衡点在 Intel Haswell 上可能低至 ~128 字节。查看 Intel 的优化手册,了解有关 memcpy 的一些实施说明,以及几种不同策略的大小与周期图。 ( 标签 wiki 中的链接)。

你没有说什么 CPU,所以我只是假设最近的英特尔。

我觉得你太担心寄存器了。命中 L1 缓存的负载非常便宜。 Haswell(和 Skylake)每个时钟可以执行两次 __m256 加载(并在同一周期中进行一次存储)。在此之前,Sandybridge/IvyBridge 每个时钟可以执行两次内存操作,其中最多一次是存储。或者在理想条件下 (256b loads/stores),他们可以管理每个时钟 2x 16B 加载和 1x 16B 存储。所以 loading/storing 256b 向量比在 Haswell 上更昂贵,但如果它们在 L1 缓存中对齐且热的话仍然非常便宜。

我在评论中提到 GNU C global register variables 可能是一种可能性,但主要是 "this is technically possible in theory" 意义上的。您可能不希望在程序的整个 运行 时间内(包括库函数调用,因此您必须重新编译它们)将多个向量寄存器专用于此目的。

实际上,只需确保编译器可以内联(或至少在优化时查看)您在任何重要循环中使用的每个函数的定义。这样它就可以避免在函数调用中必须 spill/reload 矢量 reg(因为 Windows 和 System V x86-64 ABI 都没有调用保留的 YMM (__m256) 寄存器)。

请参阅 Agner Fog's microarch pdf 以了解有关现代 CPU 的微体系结构细节的更多信息,至少是可以通过实验测量和调整的细节。