【问题标题】:Efficiency in C and C++C 和 C++ 的效率
【发布时间】:2014-03-10 14:35:53
【问题描述】:

所以我的老师告诉我,我应该根据需要即时计算中间结果,而不是存储它们,因为现在处理器的速度比内存的速度快得多。

所以当我们计算一个中间结果时,我们也需要使用一些内存对吗?谁能给我解释一下?

【问题讨论】:

  • 如果我是你,我会把它留给编译器。它可能会也可能不会选择使用寄存器进行存储。
  • @Bathsheba 说的:我认为老师的意思是编译器可以使用寄存器进行立即存储,因此不必将结果存储在内存中。但是对于当今复杂的编译器,除非您反汇编代码,否则您真的无法知道。我建议你写一个两个版本的简单程序,存储中间结果而不是,看看反汇编代码有什么不同。
  • 我要补充一点,对于大多数任务,编写干净、易于理解、可维护的代码比试图从源代码中挤出小的性能提升更为重要。

标签: c++ c performance


【解决方案1】:

你的老师是对的现在处理器的速度比内存的速度快得多。访问 RAM 比访问内部内存要慢:缓存、寄存器等。

假设您要计算一个三角函数:sin(x)。为此,您可以调用一个函数(数学库提供一个,或实现您自己的),该函数正在计算值;或者您可以使用存储在内存中的查找表来获取结果,这意味着存储中间值(有点)。

调用函数会导致执行许多指令,而使用查找表会导致指令更少(获取 LUT 的地址、获取所需元素的偏移量、从地址+偏移量读取)。我n this case, storing the intermediate values is faster

但是,如果您要执行c = a+b,计算该值将比从 RAM 中的某个位置读取它快得多。请注意,在这种情况下,要执行的指令数量是相似的。

因此,虽然访问 RAM 确实较慢,但是否值得访问 RAM 而不是进行计算是一个明智的问题,需要考虑几件事:要执行的指令数,如果计算发生在循环,您可以利用架构管道、缓存内存等。 没有一个答案,您需要单独分析每种情况。

【讨论】:

  • 你试过sin函数的例子吗?我想你可能会感到惊讶......
【解决方案2】:

你老师的建议过于简单化了一个复杂的话题。

如果您将“中间”视为一个单个术语(在这个词的算术意义上),那么问问自己,您的代码是否在其他任何地方重复使用该术语? IE。如果你有这样的代码:

void calculate_sphere_parameters(double radius, double & area, double & volume)
{
    area = 4 * (4 * acos(1)) * radius * radius;
    volume = 4 * (4 * acos(1)) * radius * radius * radius / 3;
}

你是否应该写:

void calculate_sphere_parameters(double radius, double & area, double *volume)
{
    double quarter_pi = acos(1);
    double pi = 4 * quarter_pi;
    double four_pi = 4 * pi;
    double four_thirds_pi = four_pi / 3;
    double radius_squared = radius * radius;
    double radius_cubed = radius_squared * radius;

    area = four_pi * radius_squared;
    volume = four_thirds_pi * radius_cubed;    // maybe use "(area * radius) / 3" ?
}

现代优化编译器不太可能为这两者生成相同的二进制代码。我把它留给读者来决定他们更喜欢在源代码中看到什么......

对于许多简单的算术也是如此(至少,如果计算中不涉及函数调用)。除此之外,现代编译器和/或 CPU 指令集可能能够免费进行“偏移”计算,例如:

for (int i = 0; i < N; i++) {
    do_something_with(i, i + 25, i + 314159);
}

结果与以下相同:

for (int i = 0; i < N; i++) {
    int j = i + 25;
    int k = i + 314159;
    do_something_with(i, j, k);
}

因此,主要规则应该是,如果您的代码的可读性不能从创建一个新变量来保存“临时”计算的结果中受益,那么使用一个可能有点矫枉过正。
另一方面,如果您在 10 行代码中使用了十几次 i + 12345... 说出它的名字,并评论为什么这个奇怪的硬编码偏移量如此重要。

请记住,仅仅因为您的源代码 包含变量并不意味着编译器发出的二进制代码将为该变量分配内存。编译器可能会得出结论,该值甚至没有被使用(并完全放弃分配它的计算),或者它可能得出结论它“只是一个中间值”(以后再也不会使用它应该是从内存中检索)并将其存储在寄存器中,以便在“最后一次使用”后覆盖。每次需要时计算值i + 1 比从内存位置检索它要高效得多。

我的建议是:

  • 首先要保持代码可读性 - 变量太多而不是帮助而不是晦涩难懂。
  • 不必费心保存“简单”中间值 - 加/减或按 2 的幂缩放几乎是“免费”操作
  • 如果您在多个地方重复使用相同的值(“算术项”),如果计算成本很高(例如涉及函数调用、长的算术序列或大量内存访问,如数组校验和),请保存它)。

【讨论】:

    【解决方案3】:

    所以当我们计算一个中间结果时,我们也需要使用一些内存对吗?谁能给我解释一下?

    计算机中有几个级别的内存。图层看起来像这样

    1. 寄存器 - CPU 对此进行所有计算并且访问是即时的
    2. 缓存 - 与 CPU 内核紧密耦合的内存;对主系统内存的所有内存访问实际上都经过缓存,并且如果数据来自系统内存,它看起来就像是程序一样。如果数据存在于缓存中并且访问对齐良好,则访问几乎也是即时的,因此速度非常快。
    3. 主系统内存 - 通过内存控制器连接到 CPU 并由系统中的 CPU 内核共享。访问主内存会通过寻址和内存与 CPU 之间的有限带宽引入延迟

    当您使用原位计算的中间结果时,这些中间结果通常永远不会离开寄存器,或者可能只到达缓存,因此不受可用系统内存带宽的限制或被内存总线仲裁或地址生成互锁阻塞。

    【讨论】:

    • 感谢您的解释。如果中间结果非常大(例如,某些模型对象的 3D 顶点)怎么办?中间结果是否会离开寄存器和缓存并存储在主系统内存中?如果是这样,计算中间结果会更好吗?
    • 而且我们应该期望中间结果会被访问很多次,对吧?那么计算完中间结果后,中间结果会一直留在寄存器还是缓存中?
    • @laituan245:寄存器数量有限,缓存也很小。所以不,中间结果不会留在那里。 3D 模型不是您的老师所说的那种中间数据。这更多是关于相当简单计算的中间结果,也可以预先计算。
    • @laituan245:例如,假设您有一个包含 N 个条目的查找表,其中每个条目都可以通过仅基于索引的相当简单的公式来计算。说类似a(N) = 3*sin(2*N - 0.3) - tan(1/N)(这只是一个例子)。在现代 CPU 上,从内存中获取预先计算的 a(N) 需要更多的时钟周期,而不是简单地为下一步重新计算它。我不知道你是否已经知道 Big-O 表示法,但作为一个方便的规则,不应该保留在少于 20 个时钟周期内以 O(1) 计算的所有内容(对于任何少于 10 个时钟周期的计算)都不应保留在记忆中。
    • @laituan245:要确定计算所需的时钟周期数量,您必须查看生成的汇编代码,并在 CPU 手册中查看每条指令执行指令需要多少时钟周期。
    【解决方案4】:

    这让我很伤心。

    询问你的老师(或者最好不要,因为以他的编程能力水平,我不会相信他),他是否测量过它,以及有什么区别。当你为速度编程时,规则是:如果你没有测量它,并且在改变之前和之后测量它,那么你所做的纯粹是基于假设和毫无价值的。

    实际上,优化编译器会将您编写的代码转换为可能最快的机器代码。因此,代码或速度不太可能存在任何差异。

    另一方面,使用中间变量会使复杂的表达式更容易理解,更容易正确,并且使调试更容易。如果你巨大的复杂表达式给出了看起来错误的结果,中间变量可以逐位检查计算并找出错误所在。

    现在,即使他是对的并且删除中间变量使您的代码更快,即使有人关心速度差异,他也将是错误的:使您的代码可读且更易于调试可以让您获得正确工作的版本代码更快(如果它不起作用,没人关心它有多快)。现在,如果事实证明代码需要更快,您节省的时间将允许您进行更改,使其真正更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-31
      • 1970-01-01
      • 2020-02-16
      • 2011-08-04
      • 1970-01-01
      • 2010-12-22
      相关资源
      最近更新 更多