【发布时间】:2017-04-16 15:06:54
【问题描述】:
简短的问题是,如果我有一个需要两个向量的函数。一个是输入,另一个是输出(无别名)。我只能对齐其中一个,我应该选择哪一个?
更长的版本是,考虑一个函数,
void func(size_t n, void *in, void *out)
{
__m256i *in256 = reinterpret_cast<__m256i *>(in);
__m256i *out256 = reinterpret_cast<__m256i *>(out);
while (n >= 32) {
__m256i data = _mm256_loadu_si256(in256++);
// process data
_mm256_storeu_si256(out256++, data);
n -= 32;
}
// process the remaining n % 32 bytes;
}
如果in 和out 都是32 字节对齐的,那么使用vmovdqu 代替vmovdqa 不会造成任何损失。最坏的情况是两者都未对齐,并且四分之一的加载/存储将跨越缓存线边界。
在这种情况下,我可以通过在进入循环之前先处理一些元素来将其中一个与缓存行边界对齐。但是,问题是我应该选择哪个?在未对齐的加载和存储之间,哪个更糟?
【问题讨论】:
-
看看一些memcpy的实现;我认为有一种通常的方式,但我忘记了它是哪种方式。虽然也许这取决于你在做什么。对齐的加载将避免缓存行边界,因此没有加载使用延迟惩罚(如果指针增量是可预测的,则不是很相关,因为 OOO 可以在循环的其余部分之前准备好加载地址)。由于在对象外部读取通常是安全的,但写入则不是,如果您可以避免使用完整的标量版本进行清理循环,这可能会影响决策。
-
我不久前对此进行了一些测试,并确定至少在我测试过的处理器(Pentium 4、Core 2、Sandy Bridge 和 Haswell)上,对齐输入向量是明显快于对齐输出向量。你的旅费可能会改变。我不想把它作为答案发布,因为我不再拥有测试代码,不想编写它并再次运行测试,并且在任何类型的文档中都没有官方参考。因此,请投赞成票! :-)
-
@PeterCordes 直觉上我猜这是应该对齐的商店。例如,在 Haswell 上,读取带宽是存储带宽的两倍,因此处理器可以在写入前一个块的同时获取未对齐块的两个部分。并且对 Haswell 的错位处罚几乎已被消除。
-
@YanZhou 是的,加载越过页面边界(在 x86,4KB 对齐的地址上)时,加载可能会很危险。如果下一页未映射,则读取将出现段错误。
-
@CodyGray 我发现当数据大到足以使用非临时存储时,情况正好相反。因此,您将输出缓冲区对齐以使用 NT 存储。并从未对齐的输入缓冲区中读取。
标签: c++ performance x86 memory-alignment avx