CPU缓存禁止

时间:2017-11-03 14:16:53

标签: c linux caching x86

假设我拥有defacto标准x86 CPU,具有3级缓存,L1 / L2私有,并且核心之间共享L3。有没有办法分配共享内存,其数据不会缓存在L1 / L2私有缓存上,而是只缓存在L3?我不想从内存中获取数据(这太昂贵了),但我想尝试使用和不将共享数据带入私有缓存中的性能。

假设L3在核心之间共享(可能是物理索引缓存),因此不会对频繁使用的共享数据产生任何错误共享或缓存行无效。

任何解决方案(如果存在)都必须以编程方式完成,使用C和/或基于intel的CPU组件(相对现代的Xeon架构(skylake,broadwell),运行基于Linux的操作系统。

编辑:

我有延迟敏感代码,它使用一种共享内存形式进行同步。数据将在L3中,但在读取或写入时,将根据缓存包含性策略进入L1 / L2。 通过该问题的暗示,数据必须被无效,增加了不必要的(我认为)性能命中。我想看看是否可以通过一些页面策略或仅在L3中的特殊指令来存储数据。

我知道出于安全原因可以使用特殊内存寄存器来禁止缓存,但这需要CPL0权限。

EDIT2:

我正在处理在高性能系统上运行数月的并行代码。这些系统是高核心数系统(例如40-160 +核心),可定期执行需要在usecs中执行的同步。

4 个答案:

答案 0 :(得分:3)

您无法找到阻止对Intel CPU使用L1或L2的好方法:实际上,除了一些特定的场景之外,例如Peter answer中涵盖的UC内存区域(因为他们不使用L3,所以会杀死你的表现,特别是L1从根本上涉及读写。

然而,您可以使用L1和L2的相当明确定义的缓存行为来强制驱逐您只想生活在L3中的数据。在最近的英特尔架构中,L1和L2都表现为伪LRU"标准关联"缓存。通过"标准关联"我指的是您在维基百科或您的硬件101 course中读取的缓存结构,其中缓存被分为2 ^ N个集合,其中M个条目(M -way associative cache)和来自地址的N个连续位用于查找集合。

这意味着您可以准确预测哪些缓存行将在同一个集合中结束。例如,Skylake有一个8路32K L1D和一个4路256K L2。这意味着分开的高速缓存行64K将落入L1和L2上的相同集合中。通常,大量使用的值属于同一个缓存行是一个问题(缓存集争用可能会使您的缓存看起来比它实际上小得多) - 但在这里你可以利用它来发挥你的优势!

如果要从L1和L2中逐出一条线,只需将8个或更多值读取或写入距离目标线64K的其他线。根据您的基准测试(或底层应用程序)的结构,您可能甚至不需要虚拟写入:在您的内部循环中,您可以简单地使用使用16个值,所有值均为64K,并且不会返回到第一个值,直到您我们访问了另一个。这样,每一行都会自然而然地#34;在你使用它之前被驱逐。

请注意,虚拟写入不必在每个核心上相同:每个核心都可以写入"私有"虚线,这样你就不会为虚拟写入添加争用。

一些并发症:

  • 我们在这里讨论的地址(当我们说"远离目标地址时的64K")是物理地址。如果您正在使用4K页面,您可以通过写入4K的偏移来从L1中逐出,但要使其适用于L2,您需要64K 物理偏移 - 但是您不能因为每次跨越4K页面边界,你都会写入一些任意的物理页面。您可以通过确保为所涉及的缓存行使用2MB大页面来解决此问题。
  • 我说" 8 或更多"需要读/写缓存行。这是因为缓存可能使用某种伪LRU而不是精确的LRU。您必须测试:您可能会发现伪LRU就像您正在使用的模式的精确LRU一样,或者您可能会发现需要超过8次写入才能可靠地逐出。

其他一些说明:

  • 您可以使用perf公开的效果计数器来确定您实际在L1与L2与L3之间的击球频率,以确保您的技巧有效。
  • L3通常不是"标准关联缓存"而是通过散列比典型缓存更多的地址位来查看该集合。散列意味着你最终只能使用L3中的几行:你的目标线和虚拟线应该很好地分布在L3左右。如果你发现你正在使用未散列的L3,它仍然可以工作(因为L3越大,你仍然会在缓存集中扩散) - 但你必须更加小心L3的可能驱逐同样。

答案 1 :(得分:2)

x86无法通过L1D / L2而不是L3来绕过或写入存储。有NT商店绕过所有缓存。任何强制回写L3的东西也会强制回写到内存。 (例如clwb指令)。这些是针对非易失性RAM用例或非连贯DMA设计的,其中将数据提交到实际RAM非常重要。

也无法绕过L1D进行负载(除了USWC内存使用SSE4.1 movntdqa,但在其他内存类型上并不“特殊”)。根据英特尔的优化手册,prefetchNTA可以绕过L2。

执行读取的内核上的预取对于触发从其他内核回写到L3并转移到您自己的L1D中非常有用。但是,只有在您想要执行加载之前准备好地址时,这才有用。 (它有几十个周期才有用。)

Intel CPU使用共享的包含 L3缓存作为片上缓存一致性的后盾。 2插槽必须窥探另一个插槽,但支持2P以上的Xeon有窥探过滤器来跟踪移动的缓存线。

当您读取最近由另一个核心编写的行时,它在您的L1D中始终无效。 L3是包含标签的,其标签有额外的信息来跟踪哪个核心具有该行。 (即使该行在某个L1D中处于M状态,这也是正确的,这要求它在L3中无效,according to normal MESI。)因此,在您的缓存未命中检查L3标记之后,它会触发对L1具有将其写回L3缓存的行(并且可能直接将其发送到核心而不是想要它)。

Skylake-X(Skylake-AVX512)没有包含L3(它有一个更大的私有L2和一个更小的L3),但它仍然有一个包含标签的结构来跟踪哪个核心有一条线。它还使用网格而不是环网,L3延迟似乎比Broadwell差得多。

可能有用:使用直写缓存策略映射共享内存区域的延迟关键部分。 IDK如果这个补丁进入主线Linux内核,但请参阅this patch from HP: Support Write-Through mapping on x86。 (正常的政策是WB。)

还相关:Main Memory and Cache Performance of Intel Sandy Bridge and AMD Bulldozer,深入研究2插槽SnB的延迟和带宽,以及不同启动状态的缓存行。

有关Intel CPU上的内存带宽的更多信息,请参阅Enhanced REP MOVSB for memcpy,尤其是Latency Bound Platforms部分。 (只有10个LFB限制单核带宽)。

相关:What are the latency and throughput costs of producer-consumer sharing of a memory location between hyper-siblings versus non-hyper siblings?有一些实验结果,一个线程垃圾邮件写入一个位置,而另一个线程读取它。

请注意,缓存未命中本身并不是唯一的效果。你还会从核心负责的错误推测中得到很多machine_clears.memory_ordering。 (x86的内存模型是强有序的,但实际的CPU推测性地提前加载并在极少数情况下中止,在这种情况下,在加载应该“发生”之前,缓存行变为无效。

答案 2 :(得分:2)

英特尔最近宣布了一项似乎与此问题相关的新指令。该指令称为CLDEMOTE。它将数据从较高级别的缓存移动到较低级别的缓存。 (可能从L1或L2到L3,尽管规格在细节上并不精确。)“这可能会加速其他核心对线路的后续访问......”

https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf

答案 3 :(得分:-2)

我相信你不应该(也可能不会)关心,并希望共享内存在L3中。 BTW,user-space C代码在virtual address space中运行,而您的其他核心可能(通常也会)运行其他无关 process

硬件和MMU(由内核配置)将确保L3被正确共享。

  

但我想尝试使用和不将共享数据放入私有缓存中来实现性能。

据我所知(非常糟糕)最近的英特尔硬件,这是不可能的(至少不在用户区)。

也许您可能会考虑PREFETCH机器指令和__builtin_prefetch GCC内置(与您想要的相反,它会将数据带到更近的缓存中)。请参阅thisthat

BTW,内核进行preemptive调度,因此context switches可以随时发生(通常每秒几百次)。当(在上下文切换时)另一个进程相同的核心上为scheduled时,需要重新配置MMU(因为每个进程都有自己的{{3}并且缓存再次“冷”。

您可能对virtual address space感兴趣。见processor affinity。了解约sched_setaffinity(2)。见Real-Time Linux。请参阅sched(7)

我完全不确定你害怕的性能是否值得注意(我相信在用户空间中无法避免)。

也许您可能会考虑在内核空间中移动敏感代码(因此使用CPL0权限)但这可能需要数月的工作并且可能不值得付出努力。我甚至都不会尝试。

您是否考虑过其他完全不同的方法(例如,在GPCLU的OpenCL中重写它)到您的延迟敏感代码?