软件预取手动指令合理的场景
Scenarios when software prefetching manual instructions are reasonable
我在 x86 和 x86-64 上读到过 Intel 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
fetches data to all levels of the cache for inclusive caches
and to the lowest level cache for exclusive caches
_MM_HINT_T1 hint pulls the data into L2 and
not into L1d. If there is an L3 cache the _MM_HINT_T2
hints can do something similar for it
_MM_HINT_NTA, allows telling the processor to treat the prefetched cache line specially
所以有人可以描述示例 使用此指令时吗?
以及如何正确选择提示?
预取的想法基于以下事实:
- 第一次访问内存非常昂贵。
第一次访问内存地址1必须从内存中取出,然后存储在缓存层次2.
- 访问内存本质上是异步的。
CPU 不需要来自核心的任何资源来执行 load/store3 中最长的部分,因此它可以很容易地与其他任务并行完成4.
由于上述原因,在实际需要之前尝试加载是有意义的,这样当代码实际需要数据时,它就不必等待。
CPU 在找事做的时候可以走得很远,但不能任意深入,这是非常不值一提的;所以有时它需要程序员的帮助才能发挥最佳性能。
缓存层次结构,就其本质而言,是微体系结构的一个方面,而不是体系结构(阅读 ISA)。英特尔或 AMD 无法对这些指令的作用提供强有力的保证。
此外,正确使用它们并不容易,因为程序员必须清楚每条指令需要多少个周期。
最后,最新的 CPU 越来越善于隐藏内存延迟并降低它。
所以总的来说预取是熟练的汇编程序员的工作。
也就是说,唯一可能的情况是一段代码的时间必须在每次调用时保持一致。
例如,如果您知道中断处理程序总是更新状态并且它必须尽可能快地执行,那么在设置使用此类中断的硬件时预取状态变量是值得的。
关于不同级别的prefetching,我的理解是不同级别(L1-L4)对应不同的sharing和polluting ].
例如,如果执行指令的 thread/core 与将读取变量的指令相同,则 prefetch0
是好的。
然而,这将占用所有缓存中的一行,最终驱逐其他可能有用的行。
例如,当您知道您肯定会在短时间内需要数据时,您可以使用它。
prefetch1
可以让所有核心或核心组快速使用数据(取决于 L2 的共享方式)而不会污染 L1。
如果您知道您可能需要数据或者您在完成另一项任务(优先使用缓存)后将需要它,则可以使用它。
这不如将数据存储在 L1 中那么快,但比将其存储在内存中要好得多。
prefetch2
可用于消除大部分内存访问延迟,因为它将数据移动到 L3 缓存中。
它不会污染 L1 或 L2,并且在核心之间共享,因此它适用于罕见(但可能)代码路径使用的数据或为其他核心准备数据。
prefetchnta
最容易理解,是非时间走法。它避免在每个缓存行中为只访问一次的数据创建一个条目。
prefetchw/prefetchwnt1
与其他行一样,但使该行独占并使别名此行的其他核心行无效。
基本上,它使写入速度更快,因为它处于 MESI 协议的最佳状态(用于缓存一致性)。
最后,可以逐步完成预取,首先进入 L3,然后进入 L1(只针对需要它的线程)。
简而言之,每条指令都让您决定污染、共享和访问速度之间的折衷。
由于这些都需要非常仔细地跟踪缓存的使用(你需要知道它不值得在L1中创建和进入但它在L2中)使用仅限于非常特定的环境。
在现代 OS 中,不可能跟踪缓存,您可以进行预取只是为了发现您的量程已过期并且您的程序被另一个驱逐刚刚加载的行的程序所取代。
具体的例子我有点想不通了
过去,我必须尽可能一致地测量某些外部事件的时间。
我使用中断来定期监视事件,在这种情况下,我预取了中断处理程序所需的变量,从而消除了第一次访问的延迟。
预取的另一种非正统用法是将数据移动到缓存中。
如果您想测试缓存系统或从依赖缓存的内存中取消映射设备以保持数据更长一点,这将很有用。
在这种情况下,移至 L3 就足够了,并非所有 CPU 都有 L3,因此我们可能需要移至 L2。
不过我知道这些例子不是很好。
1 实际上粒度是 "cache lines" 而不是 "addresses".
2 我想你很熟悉。简而言之:它现在从 L1 到 L3/L4。 L3/L4 在核心之间共享。 L1 始终是每个内核私有的,并由内核的线程共享,L2 通常与 L1 类似,但某些型号可能在成对的内核之间共享 L2。
3 最长的部分是从RAM传输数据。计算地址和初始化事务占用资源(例如存储缓冲区和 TLB 条目)。
4 然而,正如@Leeor 和 proved by the Linux kernel developer.
所指出的,任何用于访问内存的资源都可能成为一个关键问题
我在 x86 和 x86-64 上读到过 Intel 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 fetches data to all levels of the cache for inclusive caches and to the lowest level cache for exclusive caches
_MM_HINT_T1 hint pulls the data into L2 and not into L1d. If there is an L3 cache the _MM_HINT_T2 hints can do something similar for it
_MM_HINT_NTA, allows telling the processor to treat the prefetched cache line specially
所以有人可以描述示例 使用此指令时吗?
以及如何正确选择提示?
预取的想法基于以下事实:
- 第一次访问内存非常昂贵。
第一次访问内存地址1必须从内存中取出,然后存储在缓存层次2. - 访问内存本质上是异步的。
CPU 不需要来自核心的任何资源来执行 load/store3 中最长的部分,因此它可以很容易地与其他任务并行完成4.
由于上述原因,在实际需要之前尝试加载是有意义的,这样当代码实际需要数据时,它就不必等待。
CPU 在找事做的时候可以走得很远,但不能任意深入,这是非常不值一提的;所以有时它需要程序员的帮助才能发挥最佳性能。
缓存层次结构,就其本质而言,是微体系结构的一个方面,而不是体系结构(阅读 ISA)。英特尔或 AMD 无法对这些指令的作用提供强有力的保证。
此外,正确使用它们并不容易,因为程序员必须清楚每条指令需要多少个周期。
最后,最新的 CPU 越来越善于隐藏内存延迟并降低它。
所以总的来说预取是熟练的汇编程序员的工作。
也就是说,唯一可能的情况是一段代码的时间必须在每次调用时保持一致。
例如,如果您知道中断处理程序总是更新状态并且它必须尽可能快地执行,那么在设置使用此类中断的硬件时预取状态变量是值得的。
关于不同级别的prefetching,我的理解是不同级别(L1-L4)对应不同的sharing和polluting ].
例如,如果执行指令的 thread/core 与将读取变量的指令相同,则 prefetch0
是好的。
然而,这将占用所有缓存中的一行,最终驱逐其他可能有用的行。
例如,当您知道您肯定会在短时间内需要数据时,您可以使用它。
prefetch1
可以让所有核心或核心组快速使用数据(取决于 L2 的共享方式)而不会污染 L1。
如果您知道您可能需要数据或者您在完成另一项任务(优先使用缓存)后将需要它,则可以使用它。
这不如将数据存储在 L1 中那么快,但比将其存储在内存中要好得多。
prefetch2
可用于消除大部分内存访问延迟,因为它将数据移动到 L3 缓存中。
它不会污染 L1 或 L2,并且在核心之间共享,因此它适用于罕见(但可能)代码路径使用的数据或为其他核心准备数据。
prefetchnta
最容易理解,是非时间走法。它避免在每个缓存行中为只访问一次的数据创建一个条目。
prefetchw/prefetchwnt1
与其他行一样,但使该行独占并使别名此行的其他核心行无效。
基本上,它使写入速度更快,因为它处于 MESI 协议的最佳状态(用于缓存一致性)。
最后,可以逐步完成预取,首先进入 L3,然后进入 L1(只针对需要它的线程)。
简而言之,每条指令都让您决定污染、共享和访问速度之间的折衷。
由于这些都需要非常仔细地跟踪缓存的使用(你需要知道它不值得在L1中创建和进入但它在L2中)使用仅限于非常特定的环境。
在现代 OS 中,不可能跟踪缓存,您可以进行预取只是为了发现您的量程已过期并且您的程序被另一个驱逐刚刚加载的行的程序所取代。
具体的例子我有点想不通了
过去,我必须尽可能一致地测量某些外部事件的时间。
我使用中断来定期监视事件,在这种情况下,我预取了中断处理程序所需的变量,从而消除了第一次访问的延迟。
预取的另一种非正统用法是将数据移动到缓存中。
如果您想测试缓存系统或从依赖缓存的内存中取消映射设备以保持数据更长一点,这将很有用。
在这种情况下,移至 L3 就足够了,并非所有 CPU 都有 L3,因此我们可能需要移至 L2。
不过我知道这些例子不是很好。
1 实际上粒度是 "cache lines" 而不是 "addresses".
2 我想你很熟悉。简而言之:它现在从 L1 到 L3/L4。 L3/L4 在核心之间共享。 L1 始终是每个内核私有的,并由内核的线程共享,L2 通常与 L1 类似,但某些型号可能在成对的内核之间共享 L2。
3 最长的部分是从RAM传输数据。计算地址和初始化事务占用资源(例如存储缓冲区和 TLB 条目)。
4 然而,正如@Leeor 和 proved by the Linux kernel developer.