繁体   English   中英

软件预取手动指令是合理的情况

[英]Scenarios when software prefetching manual instructions are reasonable

我在x86和x86-64上已经了解到这一点,英特尔gcc提供了特殊的预取指令:

#include <xmmintrin.h>
enum _mm_hint
{
_MM_HINT_T0 = 3,
_MM_HINT_T1 = 2,
_MM_HINT_T2 = 1,
_MM_HINT_NTA = 0
};
void _mm_prefetch(void *p, enum _mm_hint h);

程序可以在程序中的任何指针上使用_mm_prefetch内在函数。 并且与_mm_prefetch内在函数一起使用的不同提示是实现定义的。 一般来说,每个提示都有其自身的含义。

_MM_HINT_T0将数据提取到包含高速缓存的所有高速缓存级别以及用于独占高速缓存的最低级别高速缓存

_MM_HINT_T1提示将数据拉入L2而不是L1d。 如果有一个L3缓存, _MM_HINT_T2提示可以为它做类似的事情

_MM_HINT_NTA ,允许告诉处理器专门处理预取的缓存行

那么当有人使用这条指令时,有人会描述一些例

以及如何正确选择提示?

预取的想法基于以下事实:

  • 第一次访问内存非常昂贵。
    第一次访问存储器地址1必须从存储器中取出,然后将其存储在高速缓存层次结构2中
  • 访问内存本质上是异步的。
    CPU不需要来自核心的任何资源来执行加载/存储3的最长部分,因此可以与其他任务4并行地轻松完成。

由于上述原因,在实际需要之前尝试加载是有意义的,这样当代码实际需要数据时,它就不必等待。
在寻找要做的事情时,CPU可以走得很远,但不是任意深度,这是非常值得的。 所以有时它需要程序员的帮助才能达到最佳状态。

就其本质而言,缓存层次结构是微架构的一个方面,而不是架构(读取ISA)。 英特尔或AMD无法对这些指令的作用给予强有力的保证。
此外,正确使用它们并不容易,因为程序员必须清楚每条指令可以采用多少个周期。 最后,最新的CPU越来越擅长隐藏内存延迟并降低内存延迟。
因此,通常预取是熟练的汇编程序员的工作。

这就是说唯一可能的情况是一段代码的时间必须在每次调用时保持一致。
例如,如果您知道中断处理程序总是更新状态并且它必须尽可能快地执行,那么在设置使用此类中断的硬件时,值得预取状态变量。

关于不同级别的预取,我的理解是不同的级别(L1-L4)对应于不同的共享污染量。

例如,如果执行指令的线程/核心与读取变量相同,则prefetch0是好的。
但是,这将在所有缓存中占用一条线,最终驱逐其他可能有用的线。 例如,当您知道您确实需要数据时,可以使用它。

prefetch1可以使数据快速可用于所有核心或核心组(取决于如何共享L2)而不会污染L1。
如果您知道自己可能需要数据,或者在完成其他任务(优先使用缓存)后需要数据,则可以使用此项。
这不如在L1中使用数据那么快,但比在内存中使用数据要好得多。

prefetch2可用于取出大部分内存访问延迟,因为它会移动L3缓存中的数据。
它不会污染L1或L2并且它在内核之间共享,因此它很适合稀有(但可能)代码路径使用的数据或为其他内核准备数据。

prefetchnta是最容易理解的,它是一种非暂时的举动。 它避免在每个缓存行中为只访问一次的数据创建一个条目。

prefetchw/prefetchwnt1与其他类似,但使行专用并使其他核心行无效。
基本上,它使写入速度更快,因为它处于MESI协议的最佳状态(缓存一致性)。

最后,可以逐步完成预取,首先移入L3,然后移入L1(仅适用于需要它的线程)。

简而言之,每条指令都可以让您决定污染,共享和访问速度之间的妥协。
由于这些都需要非常仔细地跟踪缓存的使用(您需要知道它不值得在L1中创建和输入,但它在L2中),因此仅限于非常特定的环境。
在现代操作系统中,无法跟踪缓存,您可以执行预取只是为了找到您的量程已过期而您的程序被另一个驱逐刚刚加载的线路的程序取代。


至于一个具体的例子,我有点想法。
在过去,我必须尽可能一致地测量一些外部事件的时间。
我使用和中断来定期监视事件,在这种情况下,我预取了中断处理程序所需的变量,从而消除了第一次访问的延迟。

另一种非正统的预取用法是将数据移动到缓存中。
如果要测试缓存系统或从依赖缓存的内存中取消映射设备以使数据保持更长时间,这将非常有用。
在这种情况下,移动到L3就足够了,并非所有CPU都有L3,所以我们可能需要转移到L2。

但我知道这些例子并不是很好。


1实际上,粒度是“缓存行”而不是“地址”。
2我认为你很熟悉。 简而言之:它目前从L1到L3 / L4。 L3 / L4在核心之间共享。 L1每个核心始终是私有的,并由核心线程共享,L2通常类似于L1,但某些模型可能在两对核心之间共享L2。
3最长的部分是来自RAM的数据传输。 计算地址和初始化事务会占用资源(例如,存储缓冲区槽和TLB条目)。
4然而,任何用于访问内存的资源都可能成为一个关键问题,正如@Leeor所指出并由Linux内核开发人员证明的那样

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM