【问题标题】:When is a CPU cache line flushed to memory after a write?写入后何时将 CPU 缓存行刷新到内存?
【发布时间】:2013-02-22 16:16:20
【问题描述】:

我正在使用 C# 并希望尽可能避免使用 unsafe 代码。如果我有一个大小可以填充缓存行的对象或数组,并且我想写入对象的每个字段或数组的索引,CPU 是否会在刷新写入行之前等待所有写入发生或当只有一个或几个写入发生时,它会提前刷新吗?

如果我只想在对行的所有写入都发生后才进行刷新,我应该在例程结束时快速连续地执行所有写入吗?我知道 CPU 和缓存一致性协议可能对此有所不同,我正在寻找一个普遍正确的经验法则答案。

【问题讨论】:

  • 你在写什么,写到哪里,用什么写的?
  • 不,你对此有错误的想法。一方面,您无法控制 .NET 对象如何跨越缓存行。您无法直接控制地址,GC 堆分配器可以做到这一点。 x86 上只有 4 个对齐,x64 上只有 8 个对齐,并且高速缓存行是 64 字节。此外,垃圾收集器压缩堆,因此地址可以随机更改。当缓存行即将被替换时,会发生回写。其他你不能直接影响自己的东西。
  • 我知道我无法直接控制 GC 分配器如何分配我的对象,但我认为如果我有一个与缓存行大小相同的对象,那么我认为 GC 更有可能将整个对象加载到单个缓存行中,不是吗?分配器不太可能分解对象。如果 GC 想要移动它很好,但如果整个东西完全适合缓存行,我想 GC 不会在缓存行之间分解它,至少在大多数情况下不会。

标签: c# caching cpu-cache


【解决方案1】:

CPU 是否会等待所有写入发生后再刷新 写入行还是会在仅发生一次或几次写入时提前刷新?

CPU 可能会提前刷新该行,但前提是该集合处于高速缓存中其他访问的高压之下。这不太可能。缓存的结构有助于避免过早刷新最近访问的数据。

我应该在例程结束时快速连续地完成所有写入吗?

一般来说是的。 时间局部性很重要,这意味着当访问按时间紧密分组时,缓存性能最佳。其他技巧也可能适用。例如,您可以尝试通过在所需写入之前对您的结构进行虚拟写入来“加热”高速缓存行。这允许一些内存级别的并行性,其中核心在执行干预代码的同时加载缓存行。当您执行真正的写入时,缓存行在 L1 中准备就绪的可能性更大。

一般来说,对代码中的不自然行为要非常谨慎,以提高缓存性能。 缓存本身就可以很好地完成工作。您应该始终衡量任何更改前后的性能。你认为可能是一种改进实际上可能会受到伤害。如果您的程序是多线程的,那么另一个大罐蠕虫会与内核之间的缓存争用有关。

【讨论】:

  • 适当注意过早优化。我只想让我的对象通常对缓存友好,因为我正在处理大量相同大小的对象,这些对象或多或少会随机交互(游戏中的 2D 碰撞检测位置更新)。我认为将大小调整为缓存行的碰撞对象可能有助于通过避免跨多个内核不必要的颠簸来提高性能。
  • @hatch22 - 我不知道你是如何从 C# 中控制这些的,但是将对象保持在 64B 以下不会有什么坏处。如果您有多个线程,请注意空间预取。当接触线 X 时,核心的预取逻辑也可能会抓取线 X+1(和 X+2)。如果另一个核心触及 X+1,即使您小心避免直接争用,您也会受到打击。
【解决方案2】:

当然,CPU 会尝试尽可能少地访问内存,但这并不一定意味着“您的”内存块将一直保存在缓存中。

通常情况下,内存块会被读取一次并写入一次,但不能保证这一点。某些事情可能会中断您的代码,并且系统可能会决定刷新该缓存行以为其他内容腾出空间。整个内存区域甚至可以完全从内存中删除并刷新到磁盘,这样当你的代码继续运行时,它会导致页面错误,从而再次加载内存。

让您的写入时间更近当然会使内存更有可能在该操作期间保留在缓存中。

【讨论】:

  • 我明白这一点。如果操作系统内核想要在我的运行代码中间进行上下文切换和刷新缓存行,我无法控制它决定做什么,我只是想确保我自己的代码不会导致它自己的抖动行为(例如,对几个字段执行写入,然后执行大量工作,然后对其他字段执行写入,在完成所有写入之前执行繁忙工作时刷新的风险增加)。
  • @hatch22:这在很大程度上取决于内存访问代码之间的操作,而不是需要多长时间。
  • 是的,如果我在两者之间访问完全不同的东西会很麻烦。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多