【问题标题】:3D FFT with data larger than cache数据大于缓存的 3D FFT
【发布时间】:2015-06-08 22:40:59
【问题描述】:

我已经搜索了这个问题的答案,但没有找到任何可以直接帮助我的东西。

我正在使用 MKL 中包含的并行 FFT 库为非线性 PDE 开发 3D 数值积分器。

我的数组由 2^30 个数据点组成,比缓存大得多。这会导致约 50% 的缓存引用未命中,这似乎会增加大量访问内存的开销。

有什么聪明的方法可以解决这个问题吗?使用这么大的数组是否预计会有 50% 的缓存未命中?

任何帮助将不胜感激。

谢谢,

迪伦

【问题讨论】:

  • 抱歉,50% 的缓存引用未命中。我将编辑原始帖子以反映这一点

标签: c++ optimization fft cpu-cache intel-mkl


【解决方案1】:

单个 FFT 中的 2^30 个数据点相当大!

数据加上指数和输出数组比 L3 缓存大几千倍,比 L1 大几百万倍。

鉴于这种差异,人们可能会争辩说 50% 的缓存未命中率实际上相当不错,尤其是对于像 FFT 这样以非顺序方式访问内存的算法。

我认为您对此无能为力。 MKL 非常好,我确信他们已经利用了任何缓存提示指令。

您可以尝试联系 Mercury Systems Inc. (www.mrcy.com) 并询问他们有关他们的科学算法库 (SAL) 的信息。他们有编写自己的数学库的习惯,根据我的经验,他们非常擅长。他们在 PowerPC 上的 FFT 比第二好的 FFT 快 30%;相当的成就。您可以免费试用未经优化的 SAL 版本 (http://sourceforge.net/projects/opensal/)。真正为英特尔 SAL 优化的软件绝对不是免费的。

还请记住,无论算法多么聪明,对于这样大小的数据集,您总是会从根本上受制于主内存带宽,而不是缓存带宽。

GPU 可能值得一看,但您需要一个具有大量内存的 GPU 来保存 2^30 个数据点(32 位复数值 = 2gbytes,同样适用于输出数组,加上指数等)。

【讨论】:

  • 非常感谢您的回复,我给 SAL 看看,看看情况如何
  • @DylanBrown 我做了更多的挖掘工作;我认为他们还没有准备好在英特尔上做所有自己的 FFT 变体。我强烈建议您在深入研究之前向他们询问一些基准时间;它不是免费的,所以你必须在提交之前确定。
【解决方案2】:

我认为过多未命中的问题是由于缓存预取机制的失败,但不知道内存访问的细节我无法告诉你确切的原因。

您的数组非常大并不重要,50% 的未命中率过高。处理器应通过检测您正在迭代数组并提前加载您可能使用的数据元素来避免丢失。

要么数组访问的模式不规则,因此处理器中的预取器无法确定要预取的模式,要么存在缓存关联问题,即迭代中的元素可能与同一个缓存匹配插槽。

例如,假设缓存大小为 1Mb,集合关联性为 4。在此示例中,缓存将使用低 20 位映射内存到内部插槽。如果你跨步 1Mb,也就是说,你的迭代正好是 1Mb,那么低 20 位总是相同的并进入同一个缓存槽,新元素与旧元素共享同一个缓存槽。当你到达第五个元素时,所有四个位置都用完了,从那时起它只是未命中,在这种情况下,你的缓存大小实际上是一个插槽;如果您跨过缓存大小的一半,则有效插槽数为 2,这可能足以完全没有任何未命中或有 100% 或介于两者之间的任何值,具体取决于您的访问模式是否同时需要两个插槽。

为了让自己相信这一点,制作一个具有不同步幅大小的玩具程序,您会发现那些除以缓存大小或为缓存大小倍数的程序会增加未命中率,您可以使用 valgrind --tool=cachegrind

【讨论】:

  • 非常感谢您的回复。我会尝试设置一个示例并看看
  • 这个答案似乎误解了傅里叶变换是什么。每个输出值取决于每个输入值,也就是说,您有 2^30 个输出取决于 2^30 个输入。当然,您可以“定期”(连续)访问每个输出值的输入。这就是 O(N*N) 非快速傅立叶变换。 FFT 放弃“常规”访问以换取更高的数值性能。 O(N) 是 2^30,O(log N) 在这里是 30。
【解决方案3】:

您应该首先确保您知道导致缓存未命中的原因;它们可能是您编写的其他代码的错,而不是 FFT 库的错。事实上,我认为很可能是这种情况。

本文的其余部分假设 FFT 确实有问题,我们需要优化。

从 FFT 中获取数据局部性的标准技巧是

  • 将数据排列成二维数组
  • 沿每一行执行 FFT
  • 应用旋转因子
  • 做一个矩阵转置
  • 沿每一行执行 FFT

这是Cooley-Tukey algorithm,在我们考虑2^(m+n) = 2^m * 2^n的情况下。

关键是对 FFT 的递归调用要小得多,并且可能非常适合缓存。如果没有,您可以递归地应用此方法,直到 确实 适合缓存。如果您有雄心壮志,您可以进行大量基准测试来找出进行拆分的最佳方法。

因此,假设您还使用了良好的矩阵转置算法,最终结果是一个相对缓存友好的 FFT。

您正在使用的库确实应该已经这样做了。如果不是,那么一些选项是:

  • 也许它公开了足够多的低级功能,您可以告诉它以有效的方式使用 Cooley-Tukey,即使高级例程没有
  • 您可以自己实现 Cooley-Tukey,使用给定的库来执行较小的 FFT。

【讨论】:

  • 当然,这确实将缓存未命中集中在矩阵转置步骤中。
  • @MSalters: ...通过使用良好的转置算法得到缓解。
  • @Hurkyl,英特尔错过了一个窍门。后来的 Xeon 有一个叫做 QuickData 的东西,基本上是一个内部 DMA 引擎。不幸的是,它非常简单,它只是移动数据。然而,某些平台上的 DMA 引擎可以编程为在它们自己进行 DMA 传输时进行矩阵转置(转置毕竟只是数据混洗)。在 CPU 继续进行一些数学运算时,让 DMA 引擎进行矩阵转置是一个不错的技巧。
猜你喜欢
  • 1970-01-01
  • 2018-09-07
  • 2013-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
相关资源
最近更新 更多