如何在不接触缓存的情况下写入或读取内存
How to write or read memory without touching cache
在 x86 CPU 下,有没有什么方法可以在不触及 L1/L2/L3 缓存的情况下 write/read 内存?
x86 CPU 中的缓存是否完全由硬件管理?
EDIT: I want to do this because I want to sample the speed of memory and see if any part of memory's performance degrades.
CPU确实在硬件中管理自己的缓存,但 x86 为您提供了一些影响此管理的方法。
要在不缓存的情况下访问内存,您可以:
使用 x86 非时态指令,它们是为了告诉 CPU 你不会再次使用这些数据,所以没有必要将它保留在缓存。 x86 中的这些指令通常称为 movnt*(后缀根据数据类型,例如 movnti 用于将普通整数加载到通用寄存器)。还有用于流式传输的说明 loads/stores 也使用类似的技术,但更适合高 BW 流(当您连续加载整行时)。
要使用它们,要么在内联汇编中编写代码,要么使用编译器提供的内部函数,其中大多数调用该系列 _mm_stream_*
将特定区域的内存类型更改为不可缓存。既然你说你不想禁用所有缓存(这是理所当然的,因为这也包括代码、堆栈、页面映射等),你可以将你的基准数据集所在的特定区域定义为不可缓存,使用 MTRR(内存类型范围寄存器)。有几种方法可以做到这一点,您需要为此阅读一些文档。
最后一个选项是正常获取该行,这意味着它最初确实被缓存,但随后使用专用的 clflush 指令(或完整的 wbinvd,如果你想刷新整个缓存)。确保正确隔离这些操作,以便您可以保证它们已完成(当然不要将它们作为延迟的一部分进行测量)。
话虽如此,如果你想做所有这些只是为了计时你的内存读取,你可能会得到不好的结果,因为大多数 CPUs 处理非临时或不可缓存的访问 "inefficiently".如果您只是在强制从内存中进行读取之后,最好通过顺序访问大到无法放入任何缓存的数据集来操纵缓存 LRU 来实现这一点。这将使大多数 LRU 方案(不是全部!)首先删除最旧的行,因此下次您环绕时,它们必须来自内存。
请注意,要使其正常工作,您需要确保您的硬件预取器没有帮助(并且不小心覆盖了您想要测量的延迟)- 要么禁用它,要么使访问步幅足够大以使其成为无效。
Leeor preety 为您的任务列出了最“pro”的解决方案。我将尝试添加另一个可以达到相同结果的提案,并且可以用简单的代码用纯 C 语言编写。这个想法是制作一个类似于 HPCC Challenge 基准中的 "Global Random Access" 的内核。
内核的想法是 随机跳转通过一个巨大的数组 的 8B 值,通常是物理内存大小的 1/2(所以如果你有 16 GB的 RAM 你需要一个 8GB 的阵列导致 8B 的 1G 元素)。对于每次跳跃,您都可以读取、写入或 RMW 目标位置。
这很可能测量 RAM 延迟,因为在 RAM 中随机跳转 使缓存效率非常低。您将获得极低的缓存命中率,如果您对阵列进行足够的操作,您将能够衡量内存的实际性能。此方法还使预取非常无效,因为没有可检测的模式。
您需要考虑以下事项:
- 确保编译器不会优化您的内核循环(确保对该数组执行某些操作或使用您从中读取的值进行操作)。
- 使用一个非常简单的随机数生成器,不要将目标地址存储在另一个数组(将被缓存)中。我用了一个Linear Congruential Generator。这样下一个地址的计算速度非常快,除了 RAM 的延迟外,不会增加额外的延迟。
在 x86 CPU 下,有没有什么方法可以在不触及 L1/L2/L3 缓存的情况下 write/read 内存?
x86 CPU 中的缓存是否完全由硬件管理?
EDIT: I want to do this because I want to sample the speed of memory and see if any part of memory's performance degrades.
CPU确实在硬件中管理自己的缓存,但 x86 为您提供了一些影响此管理的方法。
要在不缓存的情况下访问内存,您可以:
使用 x86 非时态指令,它们是为了告诉 CPU 你不会再次使用这些数据,所以没有必要将它保留在缓存。 x86 中的这些指令通常称为 movnt*(后缀根据数据类型,例如 movnti 用于将普通整数加载到通用寄存器)。还有用于流式传输的说明 loads/stores 也使用类似的技术,但更适合高 BW 流(当您连续加载整行时)。 要使用它们,要么在内联汇编中编写代码,要么使用编译器提供的内部函数,其中大多数调用该系列 _mm_stream_*
将特定区域的内存类型更改为不可缓存。既然你说你不想禁用所有缓存(这是理所当然的,因为这也包括代码、堆栈、页面映射等),你可以将你的基准数据集所在的特定区域定义为不可缓存,使用 MTRR(内存类型范围寄存器)。有几种方法可以做到这一点,您需要为此阅读一些文档。
最后一个选项是正常获取该行,这意味着它最初确实被缓存,但随后使用专用的 clflush 指令(或完整的 wbinvd,如果你想刷新整个缓存)。确保正确隔离这些操作,以便您可以保证它们已完成(当然不要将它们作为延迟的一部分进行测量)。
话虽如此,如果你想做所有这些只是为了计时你的内存读取,你可能会得到不好的结果,因为大多数 CPUs 处理非临时或不可缓存的访问 "inefficiently".如果您只是在强制从内存中进行读取之后,最好通过顺序访问大到无法放入任何缓存的数据集来操纵缓存 LRU 来实现这一点。这将使大多数 LRU 方案(不是全部!)首先删除最旧的行,因此下次您环绕时,它们必须来自内存。
请注意,要使其正常工作,您需要确保您的硬件预取器没有帮助(并且不小心覆盖了您想要测量的延迟)- 要么禁用它,要么使访问步幅足够大以使其成为无效。
Leeor preety 为您的任务列出了最“pro”的解决方案。我将尝试添加另一个可以达到相同结果的提案,并且可以用简单的代码用纯 C 语言编写。这个想法是制作一个类似于 HPCC Challenge 基准中的 "Global Random Access" 的内核。
内核的想法是 随机跳转通过一个巨大的数组 的 8B 值,通常是物理内存大小的 1/2(所以如果你有 16 GB的 RAM 你需要一个 8GB 的阵列导致 8B 的 1G 元素)。对于每次跳跃,您都可以读取、写入或 RMW 目标位置。
这很可能测量 RAM 延迟,因为在 RAM 中随机跳转 使缓存效率非常低。您将获得极低的缓存命中率,如果您对阵列进行足够的操作,您将能够衡量内存的实际性能。此方法还使预取非常无效,因为没有可检测的模式。
您需要考虑以下事项:
- 确保编译器不会优化您的内核循环(确保对该数组执行某些操作或使用您从中读取的值进行操作)。
- 使用一个非常简单的随机数生成器,不要将目标地址存储在另一个数组(将被缓存)中。我用了一个Linear Congruential Generator。这样下一个地址的计算速度非常快,除了 RAM 的延迟外,不会增加额外的延迟。