如果有一个64字节的缓冲区被大量读取/写入,则很可能将其保存在L1中;但是有没有办法强迫这种行为?
在这种情况下,给一个内核以独占方式访问这64个字节,并告诉它不要与其他内核或内存控制器同步数据,以使这64个字节始终位于一个内核的L1中,而不管CPU是否认为它是经常使用。
答案 0 :(得分:6)
否,x86不允许您这样做。您可以使用clfushopt
强制退出,或者(在即将到来的CPU上)强制回写而不能使用clwb
强制退出,但是您不能将行固定在缓存中或禁用一致性。
您可以将整个CPU(或单个内核?)置于“ RAM缓存”(aka no-fill)模式,以禁用与内存控制器的同步,并禁止回写数据。 Cache-as-Ram (no fill mode) Executable Code。在配置内存控制器之前,BIOS /固件通常在早期引导中使用它。它不是每行可用的,并且几乎可以肯定在这里实际上没有用。有趣的事实:invd
的用例之一就是退出此模式,与wbinvd
相对,它丢弃了没有写回的缓存数据。
我不确定无填充模式是否会阻止从L1d驱逐到L3或其他情况;还是只是在逐出数据时丢弃了数据。因此,您只需要避免访问超过7条其他高速缓存行,这些行将成为您在L1d中关心的行的别名,或者等同于L2 / L3。
能够强制一个内核无限期地挂接到L1d线路上,并且不响应MESI要求回写/共享它,这会使其他内核如果碰到那条线路就容易受到锁定的影响。因此,显然,如果存在这样的功能,则将需要内核模式。 (并且使用硬件虚拟化,需要管理程序特权。)它也可能阻止硬件DMA(因为现代的x86具有与缓存相关的DMA)。
因此,要支持这种功能,将需要CPU的许多部分来处理不确定的延迟,如果存在这种情况,当前可能存在一些上限,该上限可能短于PCIe超时。 (我不写驱动程序也不构建真正的硬件,只是猜测一下。)
正如@fuz所指出的,违反一致性的指令(xdcbt
)是tried on PowerPC (in the Xbox 360 CPU),由于错误推测执行该指令会造成灾难性的结果。因此,实施起来困难。
如果该线路经常使用,则更换LRU将使其保持高温。而且,如果它以足够频繁的间隔从L1d中丢失,那么它可能会在L2中保持高温,而L2在最近的设计中也处于内核和私有状态,而且速度非常快(自Nehalem以来为Intel)。英特尔在Skylake-AVX512以外的CPU上具有独占性的L3,意味着留在L1d中也意味着留在L3中。
所有这些都意味着,对于一个内核大量使用的线路,无论采用哪种频率,完全高速缓存都不会丢失到DRAM。因此吞吐量应该不是问题。 我想您可能希望将其用于实时延迟,因为一次调用一个函数的最坏情况下的运行时间很重要。在代码的其他部分从缓存行读取虚拟数据可能有助于使其保持高温。
但是,如果L3缓存中其他内核的压力导致该行从L3驱逐,则具有包含L3的Intel CPU也必须强制从仍然很热的内部缓存中驱逐。 IDK,如果有任何机制可以让L3知道核心的L1d中大量使用线路,因为这不会产生任何L3流量。
我不知道这在实际代码中有很多问题。 L3具有高度的关联性(例如16或24方式),因此在驱逐之前需要进行很多冲突。 L3还使用了更复杂的索引函数(类似于真实的哈希函数,而不仅仅是通过采用连续的位范围取模)。在IvyBridge和更高版本中,它还使用自适应替换策略来减轻逐出而避免触碰很多不经常重复使用的数据的情况。 http://blog.stuffedcow.net/2013/01/ivb-cache-replacement/。
另请参阅Which cache mapping technique is used in intel core i7 processor?
@AlexisWilke指出,在某些用例中,您可以使用矢量寄存器代替高速缓存行。 Using ymm registers as a "memory-like" storage location。您可以在全球范围内专门为此使用一些矢量reg。要在gcc生成的代码中获取此信息,可以使用-ffixed-ymm8
,或将其声明为易失性全局寄存器变量。 (How to inform GCC to not use a particular register)
使用ALU指令或存储转发将数据从向量reg获取到/从向量reg获取数据将为您保证延迟,而不会丢失数据缓存。但是,代码缓存未命中仍然是极低延迟的问题。
答案 1 :(得分:3)
没有直接的方法可以在Intel和AMD x86处理器上实现这一目标,但是您可以花些力气来接近目标。首先,您说过您担心高速缓存行可能会从L1中退出,因为其他一些内核可能会访问它。这只能在以下情况下发生:
还有其他原因可能导致线路从L1撤出,我将在稍后讨论。
如果该行是共享的,则不能禁用一致性。但是,您可以做的是对其进行私有复制,这实际上会禁用一致性。如果这样做可能导致错误的行为,那么您唯一可以做的就是将共享该行的所有线程的亲和力设置为在超线程(SMT)Intel处理器的同一物理核心上运行。由于L1是在逻辑核心之间共享的,因此该行不会由于共享而被逐出,但是由于其他原因它仍然会被逐出。
设置线程的亲和力并不能保证其他线程无法安排在同一内核上运行。为了降低在同一内核上调度其他线程(不访问该行)或重新调度该线程以在其他物理内核上运行的可能性,您可以增加线程(或共享该行的所有线程)的优先级
英特尔处理器主要是2路超线程,因此您只能运行两个共享同一行的线程。因此,如果您使用线程的亲和力和优先级,性能可能会以有趣的方式改变。您必须对其进行测量。最近的AMD处理器也支持SMT。
如果该行是专用的(只有一个线程可以访问它),则在Intel处理器的同级逻辑内核上运行的线程可能会导致该行被驱逐,因为L1是竞争性共享的,具体取决于它的内存访问行为。我将讨论如何尽快解决这个问题。
另一个问题是中断和异常。在Linux以及其他操作系统上,您可以配置哪些内核应处理哪些中断。我认为将所有中断映射到所有其他内核是可以的,除了周期性的定时器中断,其中断处理程序的行为取决于操作系统,使用它可能并不安全。根据您要为此付出多少努力,可以执行精心设计的实验,以确定计时器中断处理程序对L1D缓存内容的影响。另外,您应该避免例外。
我可以想到导致行无效的两个原因:
替换策略通常是不可配置的,因此您应努力避免冲突L1未命中,这取决于放置策略,而依赖于微体系结构。在Intel处理器上,L1D通常同时进行虚拟索引和物理索引,因为用于索引的位不需要转换。由于您知道所有内存访问的虚拟地址,因此可以确定从哪个缓存集分配哪些行。您需要确保映射到同一集合的行数(包括您不希望收回的行)不超过高速缓存的关联性。否则,您将受替换政策的支配。还要注意,L1D预取器还可以更改缓存的内容。您可以在Intel处理器上禁用它,并在两种情况下评估其影响。我想不出一种简单的方法来处理包含性较低级别的缓存。
我认为在缓存中“固定”一行的想法很有趣并且很有用。它是缓存和暂存器内存之间的混合体。该行就像是映射到虚拟地址空间的临时寄存器。
这里的主要问题是您想两者读取并写入该行,同时仍将其保留在缓存中。目前不支持这种行为。