当前位置: 首页 > 知识库问答 >
问题:

哪种对齐导致了这种性能差异

吴欣然
2023-03-14

我正在为(T

我想了解发生了什么事。

我正在使用谷歌基准测试。它旋转循环,直到确定时间为止。

主要基准代码:

using T = int;
constexpr std::size_t size = 10'000 / sizeof(T);

NOINLINE std::vector<T> const& data()
{
    static std::vector<T> res(size, T{2});
    return res;
}

INLINE void double_elements_bench(benchmark::State& state)
{
   auto v = data();

   for (auto _ : state) {
       for (T& x : v) x = x + x;
       benchmark::DoNotOptimize(v.data());
   }
}

然后,我从基准测试驱动程序的多个实例调用double\u elements\u bench。

  • 处理器:intel 9700k
  • 编译器:clang ~ 14,从主干构建
  • 选项:<代码>-mavx2-std=c 20-stdlib=libc-DNDEBUG-g-Werror-Wall-Wextra-Wpedantic-Wno弃用副本-O3

我确实将所有函数与128对齐以进行尝试,但没有效果。

当复制2次时,我得到:

------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
double_elements_0        105 ns          105 ns      6617708
double_elements_1        105 ns          105 ns      6664185

Vs重复3次:

------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
double_elements_0       64.6 ns         64.6 ns     10867663
double_elements_1       64.5 ns         64.5 ns     10855206
double_elements_2       64.5 ns         64.5 ns     10868602

这也会在更大的数据量上复制。

我寻找我知道的与代码对齐相关的计数器

LSD缓存(几年前由于某些安全问题在我的机器上关闭)、DSB缓存和分支预测器:

<代码>LSD。UOPS,idq。dsb\U uops,uops\U发布。任何、分支、分支未命中

慢速情况

------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
double_elements_0        105 ns          105 ns      6663885
double_elements_1        105 ns          105 ns      6632218

 Performance counter stats for './transform_alignment_issue':

                 0      LSD.UOPS                                                    
    13,830,353,682      idq.dsb_uops                                                
    16,273,127,618      UOPS_ISSUED.ANY                                             
       761,742,872      branches                                                    
            34,107      branch-misses             #    0.00% of all branches        

       1.652348280 seconds time elapsed

       1.633691000 seconds user
       0.000000000 seconds sys 

快速案例

------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
double_elements_0       64.5 ns         64.5 ns     10861602
double_elements_1       64.5 ns         64.5 ns     10855668
double_elements_2       64.4 ns         64.4 ns     10867987

 Performance counter stats for './transform_alignment_issue':

                 0      LSD.UOPS                                                    
    32,007,061,910      idq.dsb_uops                                                
    37,653,791,549      UOPS_ISSUED.ANY                                             
     1,761,491,679      branches                                                    
            37,165      branch-misses             #    0.00% of all branches        

       2.335982395 seconds time elapsed

       2.317019000 seconds user
       0.000000000 seconds sys

在我看来,两者都差不多。

代码:https://github.com/DenisYaroshevskiy/small_benchmarks/blob/ade1ed42fc2113f5ad0a4313dafff5a81f9a0d20/transform_alignment_issue.cc#L1

我认为这可能是对malloc返回的数据的对齐

0x4f2720为快速,0x8e9310为慢速

因此,由于clang没有对齐,我们得到了未对齐的读/写。我在一个对齐的变换上进行了测试-似乎没有这种变化。

有没有办法证实这一点?

共有1个答案

易衡
2023-03-14

是的,数据错位可以解释您对适合L1d的小数组的2倍减速。您会希望,由于每个其他加载/存储都是缓存行拆分,如果拆分的加载或存储需要2次访问L1d而不是1次,它可能只会减慢1.5倍,而不是2倍。

但它有额外的影响,比如根据加载结果重播UOP,这显然是问题的其余部分的原因,要么使无序的exec无法重叠工作并隐藏延迟,要么直接遇到瓶颈,如“拆分寄存器”。

<代码>ld\U块。no\u sr统计缓存线拆分加载被临时阻止的次数,因为用于处理拆分访问的所有资源都在使用中。

当负载执行单元检测到负载跨缓存线拆分时,它必须将第一部分保存在某个位置(显然是在“拆分寄存器”中),然后访问第二条缓存线。在像您这样的Intel SnB系列CPU上,这种第二次访问不需要RS再次将加载uop发送到端口;负载执行单元只是在几个周期后执行。(但可能无法在与第二次访问相同的周期内接受另一个负载。)

  • https://chat.stackoverflow.com/transcript/message/48426108#48426108-将重播等待缓存拆分加载结果的UOP

另一个因素是拆分负载的额外延迟,以及UOP等待这些负载结果的潜在重播,但这些也是未对齐负载的直接后果。ld\u块的大量计数。no\u sr告诉您,CPU实际上用完了拆分寄存器,本来可以做更多的工作,但由于负载本身未对齐,而不仅仅是其他影响,不得不暂停。

如果您想调查详细信息,还可以查找由于ROB或RS已满而导致的前端失速,但无法执行拆分负载会使这种情况发生得更多。因此,所有后端暂停可能都是未对齐加载的结果(如果从存储缓冲区到L1d的提交也是一个瓶颈,则可能是存储)

在100KB上,我复制了这个问题:1075ns对1412ns。在1 MB上,我想我看不到它。

数据对齐通常不会对大型阵列产生太大的影响(512位向量除外)。由于缓存线(2x YMM向量)到达的频率较低,后端有时间处理未对齐的加载/存储带来的额外开销,并且仍能保持不变。硬件预取工作做得足够好,它仍然可以最大限度地利用每核L3带宽。预计适合L2但不适合L1d(如100kiB)的尺寸会产生较小的影响。

当然,大多数类型的执行瓶颈都会显示出类似的效果,即使是像未优化的代码这样简单的东西,它会为数组数据的每个向量做一些额外的存储/重新加载。因此,仅此并不能证明是错位导致了确实适合L1d的小尺寸的速度变慢,例如您的10 KiB。但这显然是最明智的结论。

代码对齐或其他前端瓶颈似乎不是问题所在;根据idq,大多数UOP来自DSB。dsb\U uops。(有很大一部分人不是,但慢与快之间的百分比差异不大。)

如何减轻“英特尔jcc勘误表”对gcc的影响?在Skylake衍生的微体系结构(如您的微体系结构)上可能很重要;甚至有可能这就是为什么你的idq。dsb\U uops与您发布的uops\U不太接近。任何

 类似资料:
  • 问题内容: 我对Java线程技术比较陌生,并且我注意到,每次使用Thread.sleep()时,我都必须捕获InterrupetdException。 哪种行为会导致这种情况,并且在具有监视器线程的简单应用程序中,我可以忽略该异常吗? 问题答案: 好吧,如果其他一些线程调用thread.interupt(),则在该线程处于休眠状态时,您将获得Exception。是的,您可能只需将try..catc

  • 在哪种情况下会发生这种情况?

  • 我正在尝试使用printf来获得以下编程输出: 关键规格是: 字段宽度始终为4 符号总是左对齐 数字总是对齐的 零没有符号 到目前为止,我还不能提出一个能够给出所需结果的printf语句。我最接近的是: 它产生: 有没有一种方法可以做到这一点,而不必做任何繁琐的字符串操作?

  • 本文向大家介绍字符串拼接有哪些方式?哪种性能好?相关面试题,主要包含被问及字符串拼接有哪些方式?哪种性能好?时的应答技巧和注意事项,需要的朋友参考一下 性能最好的是连接: 继续补充:

  • 我一直在尝试编写一个程序来实现任意域上的多项式,一种数学结构。我选择了Haskell作为编程语言,我使用了语言扩展。但是,我不明白为什么GHCi不能推导出的约束条件。 在我看来,保证是的实例,这意味着是的实例。所以调用就像调用一样,应该是合理的。此外,我已经编写了作为约束,并且的构造函数具有的形状,因此它还应该知道的类型是的实例。 显然,译员的想法不同。我哪里搞错了?