如何在不触及缓存的情况下进行内存读写

15

有没有办法在x86 CPU下不接触L1/L2/L3高速缓存的情况下读写内存?

x86 CPU中的高速缓存是否完全由硬件管理?

编辑:我想这样做是因为我想采样内存速度并查看内存性能是否下降。


如果你在谷歌上搜索“在ANSI C中读写基于x86的内存”,你会看到什么?只是好奇。(我喜欢这个链接:https://dev59.com/5XE85IYBdhLWcg3w436o) - ryyker
1
是的,这是段错误...但我不认为是“在ANSI C中读写x86基于内存的内存”导致了段错误。我想要的是在程序的正确边界内禁用缓存,并写入或读取内存。 - Michael Tong
1
@i486,我想在内核中对内存速度进行取样,并查看是否有任何部分的内存性能下降。 - Michael Tong
非时序性加载/存储?我不确定它们是否适合于计时目的。 - gsg
2
相关问题:https://dev59.com/83VD5IYBdhLWcg3wQZQg - Nathan Fellman
显示剩余3条评论
2个回答

23

CPU在硬件中确实管理其自己的缓存,但x86为您提供了一些影响此管理方式的方法。

要访问没有缓存的内存,您可以:

  1. 使用x86非暂态指令,它们意味着告诉CPU您不会再次重用这些数据,因此保留在缓存中没有意义。x86中的这些指令通常称为movnt*(后缀根据数据类型而异,例如用于将普通整数加载到通用寄存器的movnti)。还有用于流式加载/存储的指令,也使用类似的技术,但对于高BW流(连续加载完整行时)更为适合。 要使用这些,请将它们编码为内联汇编,或使用编译器提供的内部函数,其中大多数称之为_mm_stream_*系列

  2. 更改特定区域的内存类型为不可缓存。由于您声明不希望禁用所有缓存(这样也包括代码、堆栈、页面映射等),因此您可以将基准测试数据集所在的特定区域定义为不可缓存,使用MTRRs(内存类型范围寄存器)。有几种方法可以做到这一点,您需要阅读一些文档。

  3. 最后一种选择是正常获取行,这意味着它确实最初被缓存,但然后强制清除所有缓存级别使用专用的clflush指令(或完整的wbinvd如果您想刷新整个缓存)。确保正确地隔离这些操作,以便您可以保证它们已完成(当然不要将它们作为延迟的一部分进行测量)。

话虽如此,如果您只是想计时读取内存,那么您可能会得到糟糕的结果,因为大多数CPU处理非暂态或不可缓存的访问方式“效率低下”。如果您只是为了强制从内存中读取数据,则最好通过顺序访问足够大的数据集来操纵缓存LRU,以使大多数LRU方案(并非全部!)首先删除最旧的行,因此下次您再次进行包装时,它们必须来自内存。

需要注意的是,为了使其起作用,您需要确保硬件预取器不起作用(并且意外覆盖您想要测量的延迟) - 要么禁用它,要么使访问跨度足够大,以使其无效。


请注意,clflush 命令是相对较新的。我认为它只在服务器上可用。 - Nathan Fellman
谢谢!由于理想情况下我会尽量避免修改应用程序的代码,所以2和3似乎更有帮助。我会尝试它们! - Michael Tong
这是英特尔指令集指南中非时间相关movs列表 - raphinesse
NT 存储器绕过缓存,NT 载入(movntdqa)除非在 WC 存储器上使用,否则不会绕过缓存。当前的 CPU 仍然忽略普通(WB)存储器区域上的 NT 提示。 - Peter Cordes

8

Leeor已经列出了与您的任务相关的最“专业”解决方案。我会尝试提出另一个建议,可以使用简单的C代码实现同样的结果。这个想法是创建一个内核,类似于在HPCC Challenge基准测试中发现的“全局随机访问”。

内核的想法是通过大量的8B值数组随机跳转,通常该数组大小为物理内存的1/2(因此如果您有16 GB的RAM,则需要一个8GB的数组导致8B的1G元素)。对于每个跳跃,您可以读取、写入或RMW目标位置。

这很可能测量RAM延迟,因为随机跳跃使缓存非常低效。你将得到极低的缓存命中率,如果你对数组进行足够的操作,你将能够测量内存的实际性能。这种方法还使预取非常低效,因为没有可检测的模式。

您需要考虑以下问题:

  1. 确保编译器不会优化掉您的内核循环(确保在该数组上执行某些操作或对从中读取的值进行某些处理)。
  2. 使用非常简单的随机数生成器,并且不要将目标地址存储在另一个数组中(将被缓存)。我使用了线性同余生成器。这样,下一个地址可以非常快速地计算出来,并且除RAM之外不会添加额外的延迟。

谢谢,但我尝试在后台测量速度并尽可能少地影响应用程序的性能,因此占用太多内存对我的情况来说不是很好。然而,这对于基准测试是一个好主意,我可以使用它来评估实现。 - Michael Tong
3
请记住,大多数现代“大型”CPU(通常在智能手机或更大设备中使用)可以允许多个内存请求。因此,如果您随机访问一个大数组,使用像LCG这样的东西,您将不能测量真正的内存访问延迟,因为CPU会“排队”N次访问,这些访问将大部分并行执行。在最近的英特尔CPU上,N的值约为10(搜索“行缓冲器”),因此您可能会测量到小于真实延迟的值的1/10。要测量真正的延迟,请确保每个内存访问都取决于之前的访问。 - BeeOnRope
2
一个简单的方法是将数组全部设置为零,然后将最后一次查找的结果与LCG的结果相加。由于它始终为零,因此不会影响结果,但它将强制CPU在继续下一个内存访问之前解决每个内存访问。您也可以更高级一些,通过预先使用随机值填充数组并将其用作随机函数来实现。这将从计时循环中删除LCG的开销。 - BeeOnRope

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接