【问题标题】:How much performance loss for MOVNTSS?MOVNTSS 有多少性能损失?
【发布时间】:2018-07-05 08:06:13
【问题描述】:

要在具有 24KB 6 路组关联数据缓存的 CPU 上对 [0, 220) 中的数字进行基数排序,如果选择基数 210,每个数字只能提供 24B 缓存,所以这段代码会导致很多缓存未命中:

int *x[1024], c[1024]={0}; 
for(int i=0; i<n; i++)c[A[i]&1023]++;
for(int i=0,s=0; i<1024; i++){x[i]=B+s; s+=c[i];}
for(int i=0; i<n; i++)*(x[A[i]&1023]++)=A[i]; // each ptr require 64B+ cache

所以我想到了跳过缓存直接用MOVNTSS将值存入内存,或者模拟16B缓存用MOVNTPS存入。 MOVNTSS 和缓存模拟的性能损失如何?还是取决于什么?

【问题讨论】:

  • x 数组中的指针是否指向(大部分)连续位置?
  • 我不太了解分布
  • 只有当存储在虚拟地址空间中大部分是连续的时,您才能从 NT 存储中受益。您的处理器每个逻辑核心有 5 个写入组合缓冲区,每个缓冲区只能容纳一个高速缓存行。如果顺序写入不是到同一个高速缓存行,那么核心必须等待释放 WC 缓冲区,即使它只是部分写入。这需要直接写入主存,速度很慢。如果您有类似“可能的顺序访问”的东西,那么使用 NT 存储可能是个好主意。总的来说,我不能肯定地说。你必须测量它。
  • @HadiBrais NT 存储缓冲区是否需要等待 [写入成功] 才能准备下一次写入?

标签: assembly x86 sse cpu-cache


【解决方案1】:

movntss 仅限 AMD (SSE4A),从 K10 开始支持。不过,在 Bulldozer-family 和 Ryzen 上,它比 movntps 慢。 (每 4c 吞吐量一个,而 Ryzen 的movntps xmm 每 1c 一个。)

movnti(来自整数寄存器)与 AMD Piledriver (2c)、Steamroller (1c) 和 Ryzen (1c) 上的 movntps xmm 具有相同的吞吐量。 movnti 是 SSE2 的一部分,因此它在 Intel CPU 上可用(并且高效)。

您的数字是整数(无论如何您都需要将它们放在整数寄存器中以使用低位作为数组索引),所以如果您打算为此使用 NT 存储,您会使用movnti 而不是movntss


在具有 24KB 6 路组关联数据缓存的 CPU 上

所有具有 SSE2 的 CPU 都有更大的二级缓存,您需要考虑这些缓存。 L2 命中比 RAM 快得多。

这是一个非常独特的尺寸。您有一个 Intel Silvermont 或有序 AtomBonnellSaltwell),具有 24kiB L1D 和至少 512 KiB L2 缓存(每个内核或在一对 corse 之间共享) .

但无论如何,根本不是 AMD,所以movss 从来都不是一个选择。 AMD 的低功耗Bobcat / Jaguar 有普通的 32k L1d 缓存,它们的主流内核有 64kiB (K8/K10)、16kiB (Bulldozer-family) 或 32kiB (Ryzen) L1d 缓存,并且都有更大的 L2 缓存。


更重要的是,回写 L1d + L2 缓存将有效地为您的输出存储桶提供写组合。我认为你根本不想要 NT 商店。

您确实需要您的 int *x[] 数组在 L1d 中保持热状态,因为您在循环中读取-修改-写入它。但我认为这通常会发生在普通的 LRU 缓存算法中。


NT 存储在输出流太多的情况下很糟糕。当您可以在刷新行填充缓冲区之前存储完整的缓存行时,它们是迄今为止最好的,如果内存子系统需要它来处理进出 L1d 的其他行,就会发生这种情况。

在主流 Intel 上,自 Nehalem 以来,每个内核都有 10 个 LFB。 Where is the Write-Combining Buffer located? x86。 (通过超线程,它们在内核之间共享,但 IDK 如果它是像存储缓冲区这样的静态分区或像 L1d 本身这样的竞争共享。)

在主流内核(关于 Atom/Silvermont 的 IDK)上,NT 存储在将缓存线移交给内存子系统的外部级别之前具有更高的延迟 (Enhanced REP MOVSB for memcpy),但避免 RFO 可能是一个优势。你必须测量。

我最担心的是,如果您的数据中有任何模式会导致多个不完全连续的存储到同一个存储桶,这将是非常糟糕的。模式L1d 可能已经吸收的内容对于在下一个存储可以将其加入写入组合缓冲区之前刷新的 NT 存储可能会很糟糕。


所以这段代码会导致很多缓存未命中

你最好做两次传球;第一次通过使用足够少的存储桶,以使输出箱在大多数时间都在缓存中保持热状态(至少如果您将它们倾斜以使它们不会全部命中缓存中的同一组)。

然后分别对每个桶进行排序;理想情况下,它将适合 L1d 缓存。

【讨论】:

  • MOVNTSS 是 AMD 的 SSE4a 指令集的一部分,首先在 K10 中实现,并为所有后来的 AMD 处理器提供支持。英特尔从不支持 SSE4a。 MOVNTSS 执行从 XMM 寄存器到内存的 32 位 NT 存储。 SSE4a 还提供 MOVNTSD,它是来自 XMM 的 64 位 NT 存储。
  • 每个物理内核 10 个 LFB。但是,如果该内核是超线程的,则 LFB 是静态分区 (IIRC),因此每个逻辑内核将只有 5 个 LFB。
  • 对于Atom,Intel优化手册D.2中提到内存执行子系统有8个WC缓冲区。如手册 2.5.4 中所述,英特尔酷睿等较旧的 uarch 也有 8 个填充缓冲区。我现在找不到说它们是静态分区的来源。但是为什么 Intel 在 3.6.10 中会说 Only four write- combining buffers are guaranteed to be available for simultaneous use 呢?我认为这表明它们在启用超线程时是静态分区的,并且还考虑了只有 8 个 LFB 的 uarch。
  • @HadiBrais:是的,因此通过与程序拥有整个核心的正常情况进行比较,它非常适合测试资源是静态分区还是竞争共享。 (顺便说一句,有一个 cpu_clk_thread_unhalted.one_thread_active 性能计数器,您可以使用它来验证您的测试是否始终拥有核心。)
  • 如果 LFB 是静态分区而不是竞争共享,我会感到非常惊讶。这对某些超线程场景将是一个巨大的打击,而且我不清楚为什么有必要这样做。
猜你喜欢
  • 2011-02-14
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
  • 2012-01-04
  • 2011-11-23
  • 1970-01-01
  • 1970-01-01
  • 2018-04-23
相关资源
最近更新 更多