如何在使用SIMD内部函数时在寄存器中保持输入相关的热数据

问题描述:

我正在尝试使用英特尔SIMD内在函数来加速查询答案程序。假设query_cnt依赖于输入,但始终小于SIMD寄存器计数(即,有足够的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; 
} 

我知道这是不是一个好的做法,因为存在潜在的加载/存储开销,但至少有轻微的机会vec_queries[i]可以进行优化,使他们能保存在寄存器中,但我仍然认为这不是一个好方法。

有什么更好的点子?

+0

你是否正在循环中处理多个查询而不做其他任何事情?如果不是这样,当您进入下一个查询时,您的数据将不会保留在寄存器中。或者你是否认为使用全局寄存器变量进行查询可能值得? GNU C可以做到这一点,'__m256 vec_query0 asm(“ymm0”);'应该是正确的语法IIRC。 –

+0

你有没有看过实际的asm来看看你的矢量*没有保存在寄存器中?如果幸运的话,编译器可能会优化大部分std :: vector动态分配开销。如果没有,请尝试使用固定大小的数组(因为您的大小上限较低)。 –

+0

@PeterCordes感谢您的建议。我没有看到实际的asm,但是你建议使用固定大小的数组可能是一个不错的选择,所以我可以使用'__m256 vec_query0 asm(“ymm0”)''将每个数组元素绑定到一个寄存器,但是如果我这样做了,那么一些寄存器总是会被固定元素占用,这可能导致性能损失? – MarZzz

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

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

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

我在评论中提到GNU C global register variables可能是一种可能性,但主要是在“这在理论上在技术上是可能的”意义。您可能不希望为您的程序的整个运行时间专门用于此目的的多个向量寄存器(包括库函数调用,因此您必须重新编译它们)。

实际上,只要确保编译器可以内联(或者至少在优化时看到)定义了您在任何重要循环中使用的每个函数。这样可以避免不必在函数调用中溢出/重新载入矢量寄存器(因为Windows和System V x86-64 ABI都没有保存调用YMM(__m256)寄存器)。

请参阅Agner Fog's microarch pdf了解更多关于现代CPU的微架构细节,至少可以通过实验和调整来测量的细节。

+0

我在'__m256 a'和'float b [8]'之间尝试了'memcpy'来模仿_mm256_loadu_ps和_mm256_storeu_ps,发现它确实有效。起初,我认为SIMD内部数据类型如'__m256'或'__m256i'是特殊的数据类型,可能有更多机会保存在寄存器中。现在我改变了主意,他们似乎没有什么特别的,比如'float'或'int',但是有更多的对齐限制,这是正确的吗?如果是这样,如果我用'memcpy'替换所有的SIMD'loads/stores',那么潜在的性能差异可能是什么? – MarZzz

+0

@MarZzz:是的,它们的工作方式非常类似于标量float或int类型。如果您要使用'__m256'(或'__m256i')进行更多计算,请使用加载内在函数。 (或使用另一个'__m256'的简单赋值)。如果你想移动一堆数据,使用memcpy。您不会使用memcpy将'array [i]'赋值给'float tmp',所以不要为SIMD类型执行此操作。它可能会*优化掉,但它的可读性较差,当然不会对编译器有所帮助。 –

+0

如果你对这样的东西感兴趣,看看编译器的输出。跟随编译器生成的一些代码比从零开始编写自己的程序集要容易得多,所以你不必真正知道asm来尝试这样做。将一些代码放在http://gcc.godbolt.org/上以获得[格式良好的视图](http://*.com/questions/38552116/how-to-remove-noise-from-gcc-clang-组装 - 输出)的asm(可选颜色突出显示哪个源代码行生成哪个asm行)。 –