分配更多的内存会减慢操作速度吗?

时间:2015-12-05 15:21:38

标签: c++ performance

我正在读一本名为“实时渲染”的书,作为射线球交叉算法的优化,使用了球体的平方半径,因此作者说:

  

标量r2(半径的平方)可以计算一次并存储在数据中   为了获得进一步的效率,球体的结构。在   实践这样的“优化”可能会更慢,因为那时会有更多的内存   访问,算法性能的主要因素。

这怎么可能效率不高? r2值将在函数的本地范围内访问一次,这可能比明确地caluclarting r2 = r * r慢吗?

我甚至不确定缓慢的操作是值访问还是实际将数据存储在内存中。

3 个答案:

答案 0 :(得分:4)

你真的应该基准,但作者或许想到的是CPU cache。有时(通常但不总是)重复的缓存未命中(或错误的branch prediction)会降低程序的速度。

通常情况下,当(super-scalarpipelined)处理器丢失缓存(因此L3缓存未命中)到从RAM棒获取数据时,它可能会丢失数百个周期(或纳秒),这足以进行数百次算术运算(在寄存器内的数据上,或在L1缓存内)。 因此,memoizing 简单计算可能不值得(例如,因为具有更大的结构可能需要更多的内存带宽和更多的缓存未命中)。

但是邪恶在于细节,你需要基准(并且在不同的计算机上可能会有所不同,例如在你的笔记本电脑和我的桌面上运行< em>相同的 Linux操作系统,相同的编译器和相同的二进制可执行文件),当然在编译器中启用了optimizations(例如g++ -Wall -O2 -march=nativeGCC ...)。

通过阅读computer architecture上的书籍,您将学到更多东西。

teach yourself programming in ten years上阅读这个引人深思的发人深省的页面。它还提供了典型PC上各种操作的近似时序(它已在其他地方借用)的表格。

答案 1 :(得分:2)

我记得以前就在这一点上。这不久以前,我还在了解这一点。我会尝试提供一个非正式的答案来补充已经很精确的答案。

对我来说特别有帮助的是这两个链接:

开始的最快方法是获取一个不错的分析器,它可以分解分析会话中涉及的代码库的每个小部分的执行时间。接下来,当你努力工作到更难以解决算法瓶颈的更加困难的热点时,请开始深入研究缓存未命中和分支错误预测。

之后,尽管有一个相当优化的算法以及如何处理这些热点,知道这些热点存在的原因开始变得相当自动 - 您将在自己的热点上发现这些信息。

内存层次结构

然而,简单地说,计算机体系结构具有从小型,快速,昂贵的内存到大型,慢速,廉价内存的内存层次结构。它从一个非常慢的大型硬盘一直跨越,例如,4千字节的块,然后到一个由64字节高速缓存线组成的CPU高速缓存层,一直到一个小小的寄存器中。可能只是一个8字节(64位)。而那个小小的记录就是大部分操作的执行地。

执行操作并计算结果后,通常结果必须以这种内存层次结构的方式备份,以便可以持续保存它们,同时为新内存区域的新操作腾出空间。

为了快速做到这一点,硬件通常被设计为从相当大的块中较大但较慢形式的内存中获取内存。例如,像硬盘这样的二级存储设备比DRAM要慢得多,因此计算机页面在内存中,例如4千字节块。假设即使您立即请求了一个8字节(64位块),您也可能会从周围的内存区域访问数据。因此,内存以对齐的4千字节块的形式分配到DRAM中,例如,假设(或者#34;希望&#34;)您,程序员将在以下指令集中访问大量的块。

同样的过程适用于从DRAM到CPU缓存。 CPU缓存比DRAM快得多,因此计算机倾向于将DRAM中的内存提取到相当大的块(通常是64字节对齐的缓存行)中的CPU缓存中。硬件设计者的假设/预测/希望再一次是,你会在被驱逐之前利用它。

这种过程再次从CPU缓存到寄存器重复(这比CPU缓存更快),从较慢的内存转移到更快的内存中(尽管这次只有极少数)。

<强>速度

软件的大部分速度最终都会受到内存访问的限制。它需要一个特别优化的内存布局或者在一小块内存上实际上很重的计算实际上是通过算术的瓶颈而不是通过从较慢到更快的内存形式传输数据块的过程。您可能会在实际基准测试中看到这一点,其中使用AVX指令提高了实际操作的速度,该操作主要由浮点运算组成45%。当AVX指令比标量版本快8倍时,为什么我们只得到45%?暂且不谈可能插入AVX指令的潜在编译器优化,其中很多都与内存有关。计算机可以非常快速地进行算术运算,但它们只能尽可能快地将内存加载到那些更快但很小的内存形式(寄存器,例如)。

从软件工程的角度来看,如果您可以使数据更小,连续,与寄存器,缓存行和/或页面的大小正确对齐,并以块的形式连续访问,则会有很大帮助。硬件设计人员在此进行的许多假设和优化都支持类似于阵列的顺序访问模式,在这种模式下,您可以在以更快的形式缓存之前访问连续的内存区域,然后再将其从更快的内存形式逐出为其他记忆区域的其他行动腾出空间。

  

这怎么可能效率不高? r2值将被访问一次   函数的局部范围,可能比显式慢   caluclarting r2 = r * r?

如果它存储在函数的本地范围内,则可能会将其存储并保存在整个持续时间的寄存器中。作者特别提到的是将r^2的数据作为一种记忆形式保存在一个领域。

在这种情况下,正在处理的每个球体的大小增加,因此更少的球体最终可能适合这些更快的内存块。它也可能会抛弃球体的对齐并引导您进入单个球体跨越两个对齐的缓存线的场景,可能会以这种方式击中性能。

您可以在查找表中看到类似的趋势。 LUT通常会降低性能并导致令人失望的结果,特别是如果它们相当大。我们最终交易减少的算术以增加内存访问,并且内存访问往往更快成为瓶颈。

所以它非常违反直觉,这远非专家的答案,而是机器的工作方式和它认为的昂贵的工作&#34;与我们倾向于自然地想到它的方式截然不同。开始对这些事物开发直觉并防止更多瓶颈的远见而不是事后处理的剖析器的最佳方法是开始剖析很多。那时,你将开始获得某种程度的直觉,了解什么类型的数据结构是缓存友好的,哪些不是(链接结构缺少连续的分配器,例如),尽管其中很多仍然不可避免地需要事后才被发现。甚至英特尔的开发人员都使用分析器来代码,因为硬件的复杂性已经增加到很难准确预测它将要做什么,而不是仅仅事后才意识到这一点。手中有一个分析器。

所以我的建议是试图抹去对人类来说似乎是直观的东西,而不是工作&#34;,在内存访问和参考局部方面开始更多地考虑它。这也是一个不错的初学者链接:

https://en.wikipedia.org/wiki/Locality_of_reference

答案 2 :(得分:1)

这是众所周知的空间/时间权衡。不要只考虑一个球体而是多个球体。

template<typename T> struct sphere
{
  T posx,posy;
  T r;
  T r2;

template<typename T> static sphere<T> create( T posx, T posy, T r );
}

这意味着每次都会存储额外的几个字节。为了进行计算,必须从内存中获取。显然,当有一些Ray描记发生时,这不是问题。虽然在实际场景中你会有很多领域。你可以通过缩小它们来获得收益。

相关问题