【问题标题】:cache behaviour on redundant writes冗余写入的缓存行为
【发布时间】:2011-03-19 22:33:08
【问题描述】:

编辑 - 我想我问的问题太长了,所以我把它说得很具体。

问题:如果内存位置在 L1 缓存中并且未标记为脏。假设它有一个值 X。如果你尝试将 X 写入同一个位置会发生什么?是否有任何 CPU 会看到这样的写入是多余的并跳过它?

例如,是否有比较两个值并丢弃冗余写回主存储器的优化?具体来说,主流处理器是如何处理的?当值是像 0 这样的特殊值时呢?如果即使是像0这样的特殊值也没有这样的优化,那是有原因的吗?

动机:我们有一个可以轻松放入缓存的缓冲区。多个线程可能会通过相互回收来使用它。每次使用都涉及写入缓冲区中的 n 个位置(不一定是连续的)。回收只是意味着将所有值设置为 0。每次回收时,size-n 个位置已经为 0。在我看来(直观地)避免如此多的冗余回写将使回收过程更快,并且因此问题。

在代码中这样做没有意义,因为分支指令本身可能会导致不必要的缓存未命中(if (buf[i]) {...} )

【问题讨论】:

  • 这是一个非常大的问题,高度依赖于架构。以下是有关可能与您的任务相关或至少可以帮助您更好地提出问题的技术的英特尔应用说明:software.intel.com/en-us/articles/…
  • 我认为那篇文章没有回答我的问题。我读过很多关于缓存的文章,但没有提到任何地方的相同值。非常感谢您提供具体信息。
  • 我通常主张许多性能问题可以通过思考来回答 - 但这次我很想看看一些测量的结果。 (轻推)
  • 虽然l1缓存与cpu密切相关,但这是一个内存系统/缓存问题而不是cpu问题。请参阅我在 rwongs 答案下的回复,您要么什么都不保存,要么失去一个时钟周期,并添加不必要的逻辑。 github.com/dwelch67/amber_samples 或直接访问 opencores 并获取 amber 项目。 amber25版本有缓存,开源,随意修改,看看区别。

标签: language-agnostic optimization caching computer-architecture


【解决方案1】:

您建议的硬件优化不会减少延迟。考虑最低级别的操作:

  1. 该位置的旧值从缓存加载到 CPU(假设它已经在缓存中)。
  2. 比较旧值和新值。
  3. 如果新旧值不同,则将新值写入缓存。否则将被忽略。

步骤 1 实际上可能比步骤 2 和 3 花费更长的时间。这是因为步骤 2 和 3 在步骤 1 中的旧值被带入 CPU 之前无法启动。如果用软件实现,情况也是一样。

考虑我们是否只是将新值写入缓存,而不检查旧值。它实际上比上面提到的三步过程更快,原因有两个。首先,无需等待旧值。其次,CPU 可以简单地在输出缓冲区中安排写操作。输出缓冲区可以同时执行缓存写入,而 ALU 可以开始处理其他事情。

到目前为止,所涉及的唯一延迟是 CPU 和缓存之间的延迟,而不是缓存和主内存之间的延迟。


现代微处理器的情况更加复杂,因为它们的缓存被组织成缓存行。当一个字节值写入缓存行时,必须加载完整的缓存行,因为缓存行中未被重写的其他部分必须保留其旧值。

http://blogs.amd.com/developer/tag/sse4a/

  • 缓存命中:数据从缓存行读取到目标寄存器
  • 缓存未命中:数据从内存移动到缓存,并读入目标寄存器
  • 缓存命中:数据从寄存器移动到缓存行
  • 缓存未命中:将缓存行取入缓存,将寄存器中的数据移至缓存行

【讨论】:

  • 你确实意识到在多进程机器上需要一个步骤 4 - 将值一直驱逐到主内存。该步骤将需要 10-200 个周期。我想有时节省这额外的步骤可能是值得的。
  • @hawk:你已经知道的比我多。请阅读drdobbs.com/architecture-and-design/208200273。或许你可以考虑直接问 Herb Sutter 是否知道这样的优化。
  • 驱逐到 dram 需要 200 多个周期,数百到数千才能获得 dram 中的实际价值。缓存层也可能,当你从 l1 驱逐时,你点击 l2(如果存在)它必须经历所有相同的回转,我有这个东西,我是否需要获取一个缓存行,等等。
  • rwong 在这个问题上几乎一针见血,你没有保存任何东西,你创建了不必要的逻辑,这会消耗更多的能量,增加了实现中逻辑错误的风险,等等。实际上,您通过比较来使用时钟周期,一旦命中就盲目写入更快,脏或干净。标记脏位而不费心比较它以前是什么也更快,只需编写,只需将行标记为脏,完成。这就是缓存存在的原因,不是为了让事情变得更慢而是更快。
【解决方案2】:

我不知道有任何处理器会进行您所描述的优化 - 消除不会改变值的清理缓存行的写入 - 但这是一个好问题,一个好主意,伟大的思想都一样。

我写了一个很大的回复,然后我想起来了:这在文献中被称为“沉默的商店”。参见“无声商店免费”,K. Lepak 和 M Lipasti,UWisc,MICRO-33,2000。

无论如何,在我的回复中,我描述了一些实施问题。

顺便说一句,像这样的话题经常在 USEnet 新闻组 comp.arch 中讨论。

我还在我的 wiki http://comp-arch.net 上写了关于它们的文章

【讨论】:

  • 嘿,还有一个有趣的事情:大多数现代处理器都有写回缓存,但也有少数有写直通缓存,可能与写回结合使用。例如。 AMD Bulldozer L1 缓存是 WT,一些 IBM z 系列和许多 GPU 也是如此。大多数这样的 WT 缓存都伴随着写合并或写合并。这种写合并缓存或缓冲区自然消除了“静默存储”——它们完成到 L1$,但不会写入 L2$。
【解决方案3】:

这不是您最初关于计算机体系结构问题的答案,但可能与您的目标相关。

在这个讨论中,所有的数组索引都是从零开始的。


假设 n 远小于 size,请更改您的算法以保存两条信息:

  1. 大小的数组
  2. 一个 n 数组和一个计数器,用于模拟一个集合容器。允许重复值。

每次向全尺寸数组中的索引 k 写入非零值时,将值 k 插入到集合容器中。

当需要清空全长数组时,获取set容器中存储的每个值(其中会包含k等),并在全长数组中设置每个对应的索引归零。


也可以使用称为两级直方图或基数直方图的类似技术。

存储两条信息:

  1. size 的数组
  2. ceil(size / M) 的布尔数组,其中 M 是基数。 ceil 是天花板函数。

每次将非零值写入全尺寸数组中的索引 k 时,都应标记布尔数组中的元素 floor(k / M)

假设 bool_array[j] 已标记。这对应于全尺寸数组中从 j*M(j+1)*M-1 的范围。

当需要清空全长数组时,扫描布尔数组中是否有标记的元素,并清空全长数组中对应的范围。

【讨论】:

  • 我不是在寻找软件中的技术——在软件中我不知道缓存中什么时候有东西。
  • @hawk:我的技术减少了实现相同算法所需的写入总数。所以我认为它可能对您有用,但不依赖于特定的计算机架构。
猜你喜欢
  • 2018-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多