CPU缓存抑制

wwwo4jvm  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(117)

假设我有事实上的标准x86 CPU,具有3级缓存,L1/L2私有,L3在内核之间共享。有没有一种方法来分配共享内存,使其数据不会缓存在L1/L2私有缓存上,而是只缓存在L3上?我不想从内存中获取数据(这太昂贵了),但我想试验一下将共享数据放入私有缓存和不放入私有缓存的性能。
假设L3在核之间共享(假定物理索引的高速缓存),因此不会导致任何错误共享或高速缓存行无效,用于大量使用的共享数据。
任何解决方案(如果存在的话)都必须通过编程来完成,使用C和/或基于英特尔的CPU(相对现代的至强架构(skylake,broadwell))的汇编,运行基于Linux的操作系统。
编辑:
我有延迟敏感的代码,它使用一种形式的共享内存进行同步。数据将在L3中,但当读取或写入时,将根据缓存包容性策略进入L1/L2。由于这个问题,数据将不得不被无效化,从而增加了不必要的(我认为)性能损失。我想看看是否有可能只存储数据,无论是通过一些页面策略还是只在L3中的特殊指令。
我知道出于安全原因,可以使用特殊的内存寄存器来禁止缓存,但这需要CPL 0权限。
编辑2:
我处理的是在高性能系统上运行数月的并行代码。这些系统是高核心数系统(例如,40-160+核),其周期性地执行需要在usecs中执行的同步。

hiz5n14c

hiz5n14c1#

x86无法进行绕过或写入L1 D/L2但在L3停止的存储。有一些NT存储可以绕过所有缓存。任何强制写回L3的操作也会强制写回内存。(例如clwb指令)。这些是为非易失性RAM用例或非一致性DMA而设计的,在这种情况下,将数据提交到实际RAM非常重要。
(更新:特雷蒙/蓝宝石急流有cldemote。早期的硬件将其作为NOP运行,因此它可以用作提示。)
也没有办法绕过L1 D(除了从WC内存,如带有SSE 4.1 movntdqa的视频RAM,但它在其他内存类型上并不“特殊”)。根据英特尔的优化手册,prefetchNTA可以绕过L2。(并在Xcovery上使用非包含性L3缓存绕过L3。)
在内核上进行读操作的预取应该有助于触发从其他内核到L3的写回,并传输到您自己的L1 D。但这只有在你想加载之前准备好地址时才有用。(几十到几百个周期才有用。)
英特尔CPU使用共享的 * 包容性 * L3高速缓存作为片上高速缓存一致性的支持。双插槽必须监听另一个插槽,但支持2 P以上的Xcovery具有监听过滤器来跟踪四处移动的缓存行。(这是描述Broadwell Xeon v4,而不是Skylake和后来的Xeon Scalable的重新设计。
当您读取最近由另一个核心写入的行时,它在您的L1 D中总是无效的。L3是包含标签的,它的标签有额外的信息来跟踪哪个核心有这条线。(即使该行在L1 D的某个地方处于M状态,这也是真的,这要求它在L3中无效,according to normal MESI。)因此,在缓存未命中检查L3标记之后,它触发了对具有该行的L1的请求,以将其写回L3缓存(并且可能将其直接发送到需要它的核心)。
Skylake-X(Skylake-AVX 512)没有包含L3(它有一个更大的私有L2和一个更小的L3),但它仍然有一个标签包含结构来跟踪哪个核心有一条线。它还使用网格而不是环,L3延迟似乎比Broadwell明显更差。(特别是在第一代Skylake中;我认为在冰湖和后来的Xews中没有那么糟糕。
可能有用的:使用直写缓存策略Map共享内存区域的延迟关键部分。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,深入研究了不同起始状态下高速缓存行的双插槽SnB延迟和带宽。
有关英特尔CPU上内存带宽的详细信息,请参阅Enhanced REP MOVSB for memcpy,尤其是延迟绑定平台部分。(只有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推测性地提前加载并且在罕见的情况下中止,其中该高速缓存行在加载被认为已经“发生”之前变得无效。
还涉及:

9udxz4iz

9udxz4iz2#

您找不到禁用英特尔CPU的L1或L2的好方法:事实上,除了一些特定的场景,如Peter的answer中覆盖的UC内存区域(这会降低您的性能,因为它们也不使用L3),L1尤其基本上涉及读写。
但是,您可以做的是使用L1和L2的定义良好的缓存行为来强制驱逐您只想驻留在L3中的数据。在最近的Intel架构中,L1和L2都表现为伪LRU“标准关联”缓存。所谓“标准关联”,我指的是你在维基百科或硬件101 course中读到的该高速缓存结构,其中缓存被划分为2^N个集合,这些集合具有M条目(对于M路关联缓存),并且来自地址的N连续位用于查找该集合。
这意味着您可以准确地预测哪些缓存行将最终出现在同一集中。例如,Skylake有一个8路32 K L1 D和一个4路256 K L2。这意味着相隔64 K的高速缓存行将落入L1和L2上的同一组中。通常情况下,频繁使用的值落入同一个缓存行是一个问题(缓存集争用可能会使缓存看起来比实际小得多)-但在这里,您可以利用它来获得优势!
当你想从L1和L2中驱逐一行时,只需要在距离目标行64 K的其他行中读取或写入8个或更多的值。根据您的基准测试(或底层应用程序)的结构,您甚至可能不需要虚拟写入:在你的内部循环中,你可以简单地使用16个值,所有的值都以64 K间隔开,直到你访问了其他15个值,才返回到第一个值。这样,每一行在你使用它之前都会“自然”地被清除。
请注意,每个内核上的虚拟写入不必相同:每个内核都可以写入“专用”虚拟行,因此不会增加虚拟写入的争用。
一些并发症:

  • 我们在这里讨论的地址(当我们说“距离目标地址64 K”时)是 * 物理 * 地址。如果你使用的是4K页面,你可以通过在4K的偏移量上写来从L1中驱逐,但是要让它在L2中工作,你需要64 K * 物理 * 偏移量-但是你不能可靠地获得,因为每次你越过4K页面边界时,你都在写一些任意的物理页面。您可以通过确保为相关的缓存行使用2 MB的巨大页面来解决这个问题。
  • 我说“8 * 或更多 *”缓存行需要读/写。这是因为缓存可能使用某种伪LRU而不是精确LRU。你需要测试:您可能会发现伪LRU就像您正在使用的模式的确切LRU一样工作,或者您可能会发现需要超过8次写入才能可靠地退出。

其他注意事项:

  • 您可以使用perf公开的性能计数器来确定您在L1、L2和L3中实际命中的频率,以确保您的技巧有效。
  • L3通常不是“标准关联缓存”:而是通过散列比典型的高速缓存更多的地址位来查找该集合。散列意味着你最终不会只使用L3中的几行:你的目标线和虚拟线应该很好地分布在L3周围。如果你发现你正在使用一个未散列的L3,它应该仍然工作(因为L3更大,你仍然会分散在缓存集中)-但你必须更加小心可能从L3驱逐。
qaxu7uf2

qaxu7uf23#

英特尔最近宣布了一项新的指令,似乎与这个问题有关。该指令称为CLDEMOTE。它将数据从更高级别的缓存移动到更低级别的缓存。(可能是从L1或L2到L3,尽管规格没有精确的细节。)“这可能会加速其他核心对该线路的后续访问。
https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf

hxzsmxv2

hxzsmxv24#

我相信你不应该(可能也不能)关心,并希望共享内存在L3中。顺便说一句,user-space C代码在virtual address space中运行,而您的其他核心可能(而且经常)运行其他 * 无关 * process
硬件和MMU(由内核配置)将确保L3被正确共享。
但是我想在有和没有将共享数据带入私有缓存的情况下试验一下性能。
据我所知(相当差)最近的英特尔硬件,这是不可能的(至少在用户的土地)。
也许你可以考虑PREFETCH机器指令和__builtin_prefetch GCC内置(这与你想要的相反,它将数据带到更近的缓存)。参见thisthat
顺便说一句,内核执行preemptive调度,所以context switches可以在任何时刻发生(通常每秒几百次)。当(在上下文切换时)另一个 * 进程 * 被调度在 * 同一个 * 核心上时,MMU需要重新配置(因为每个进程都有自己的virtual address space,缓存再次“冷”)。
您可能对processor affinity感兴趣。请参见sched_setaffinity(2)。关于Real-Time Linux参见sched(7)。看numa(7)
我根本不确定你担心的性能下降是可以避免的(我相信这在用户空间是不可避免的)。
也许您可以考虑将敏感代码移到内核空间(使用CPL0特权),但这可能需要数月的工作,而且可能不值得。我都不想试。
你是否考虑过其他完全不同的方法(例如,在OpenCL中为你的GPGPU重写它)到你的延迟敏感代码?

相关问题