胜过 _intel_fast_memcpy 的矢量化 memcpy?

Vectorized memcpy that beats _intel_fast_memcpy?

我已经实现了 memcpy 的 SSE4.2 版本,但我似乎无法在 Xeon V3 上击败 _intel_fast_memcpy。我在收集例程中使用我的例程,其中每个位置的数据在 4 到 15 个字节之间变化。我在这里和英特尔网站上看了很多帖子,但没有运气。我应该看什么好的来源?

你能用 16B 的加载和存储进行收集,然后不管最后有多少垃圾字节只是重叠吗?

// pseudocode: pretend these intrinsics take void* args, not float
char *dst = something;
__m128 tmp = _mm_loadu_ps(src1);
_mm_storeu_ps(dst, tmp);
dst += src1_size;

tmp = _mm_loadu_ps(src2);
_mm_storeu_ps(dst, tmp);
dst += src2_size;

...

重叠存储是高效的(L1 缓存可以很好地吸收它们),现代 CPU 应该可以很好地处理这个问题。未对齐的 loads/stores 足够便宜,我认为您无法击败它。 (假设页面拆分加载的平均数量。即使您有超过平均数量的缓存行拆分加载,它也可能不会成为问题。)

这意味着内部循环内没有条件分支来决定复制策略,或任何掩码生成或任何东西。您所需要的只是最多 12B 的额外空间或收集缓冲区末尾的一些东西,以防最后一个副本只应该是 4B。 (您还需要您收集的元素不在页面末尾的 16B 范围内,其中下一页未映射或不可读。)

如果读完您正在收集的元素的末尾是个问题,那么 vpmaskmov 加载实际上可能是个好主意。如果您的元素是 4B 对齐的,那么读取末尾最多 3 个字节总是没问题的。您仍然可以在 dst 缓冲区中使用普通的 16B 向量存储。


我使用 _ps 加载,因为 movupsmovupdmovdqu 短 1 个字节,但执行相同(参见 Agner Fog's microarch pdf, and other links in the 标签 wiki。 (clang 有时甚至会使用 movaps / movups 作为 _mm_store_si128。)


回复:您的评论:不要使用旧版 SSE maskmovdqu。最大的问题是它只能作为一个商店,所以它不能帮助你避免在你收集的物品之外阅读。 ,它会绕过缓存(它是一个 NT 存储),使您重新加载此数据时速度极慢。

AVX 版本 (vmaskmov and vpmaskmov) 并非如此,因此将您的代码转换为使用 maskmovdqu 可能会大大降低速度。


相关:我刚才发了一篇关于的问答。我得到了一些有趣的回应。显然这通常不是解决任何问题的最佳方法,尽管我(聪明的 IMO)生成掩码的策略非常有效。

MOVMASKPS is very much one of those "it seemed like a good idea at the time" things AFAICT. I've never used it. – Stephen Canon