【问题标题】:Unaligned load versus unaligned store未对齐的加载与未对齐的存储
【发布时间】: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;
}

如果inout 都是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


【解决方案1】:

冒着风险在这里陈述显而易见的事实:除了“您需要使用实际代码和实际数据进行基准测试”之外,没有“正确答案”。无论哪种变体更快,很大程度上取决于您使用的 CPU、您在每个包上执行的计算量以及许多其他事情。

如 cmets 中所述,您还应该尝试非临时存储。有时可以帮助的是在当前循环中加载以下数据包的输入,即:

__m256i next =  _mm256_loadu_si256(in256++);
for(...){
    __m256i data = next; // usually 0 cost
    next = _mm256_loadu_si256(in256++);
    // do computations and store data
}

如果您正在执行的计算具有不可避免的数据延迟,您还应该考虑计算两个交错包(虽然这使用了两倍的寄存器)。

【讨论】:

    猜你喜欢
    • 2018-01-24
    • 2021-02-06
    • 2016-10-10
    • 2010-11-07
    • 2013-06-03
    • 1970-01-01
    • 2014-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多