繁体   English   中英

C和C ++的效率

[英]Efficiency in C and C++

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

因此,当我们计算中间结果时,我们还需要使用一些内存对吗? 有人可以向我解释吗?

您的老师说的对,当今处理器的速度比内存的速度快得多。 访问RAM较慢访问内部存储器:缓存,寄存器等。

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

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

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

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

因此,当我们计算中间结果时,我们还需要使用一些内存对吗? 有人可以向我解释吗?

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

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

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

您的老师的建议过于复杂。

如果您认为“中间”是一个术语 (从单词的算术意义上来说),请问自己,您的代码是否在其他地方重用了该术语? 即,如果您有如下代码:

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);
}

因此,主要规则应该是,如果代码的可读性没有从创建新变量来保存“临时”计算的结果中受益,则使用它可能会过头。
另一方面,如果您在十行代码中使用i + 12345十次,请给它命名,然后评论为什么这个奇怪的硬编码偏移量如此重要。

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

我的建议是:

  • 始终使您的代码具有可读性-太多变量而不是帮助而不是模糊。
  • 不必担心保存“简单”的中间体-加/减或以2的幂进行缩放几乎是“免费”的操作
  • 如果您在多个地方重复使用相同的值(“算术术语”),请在计算成本高昂(例如涉及函数调用,较长的算术序列或大量内存访问,例如数组校验和)的情况下保存该值。

这伤了我。

问问你的老师(或者更好,不要,因为他在编程方面的能力我不信任他),他是否衡量过它以及有什么不同。 进行速度编程时的规则是:如果尚未测量速度,并且在更改前后都没有测量速度,那么您所做的就是纯粹基于假设并且毫无价值。

实际上,优化的编译器将采用您编写的代码,并将其转换为最快的机器代码。 结果,代码或速度上不可能有任何差异。

另一方面,使用中间变量将使复杂的表达式更容易理解和更容易理解,并且使调试变得更加容易。 如果庞大的复杂表达式给出的结果看起来像是错误的结果,则中间变量可以使您一点一点地检查计算并找出错误所在。

现在,即使他是正确的并删除中间变量也可以使您的代码更快,即使有人关心速度差异,他也会错了:提高代码的可读性和调试性会使您更快地获得正确工作的代码版本(如果它不起作用,那么没人会在乎它有多快。 现在,如果事实证明代码需要更快,那么您节省的时间将使您进行更改,从而使其真正更快。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM