[英]Efficiency in C and C++
所以我的老师告诉我,我应该根据需要即时计算中间结果,而不是存储它们,因为如今处理器的速度比内存的速度要快得多。
因此,当我们计算中间结果时,我们还需要使用一些内存对吗? 有人可以向我解释吗?
您的老师说的对,当今处理器的速度比内存的速度快得多。 访问RAM较慢访问内部存储器:缓存,寄存器等。
假设您要计算三角函数:sin(x)。 为此,您可以调用一个 计算值 的函数 (数学库提供了一个,或者实现了自己的函数) ; 或者您可以使用存储在内存中的查找表来获取结果,这意味着存储中间值 (某种)。
调用函数将导致执行许多指令,而使用查找表将导致指令减少(获取LUT的地址,获取所需元素的偏移量,从地址+偏移量读取)。 在n this case, storing the intermediate values is faster
但是,如果要执行c = a+b
,则计算值将比从RAM中的某个位置读取值快得多。 请注意,在这种情况下,要执行的指令数将相似。
因此,虽然访问RAM的速度确实很慢,但是是否值得访问RAM而不是进行计算是一个明智的问题,需要考虑以下几件事:要执行的指令数,如果计算是在循环中进行的,那么您可以利用架构管线,缓存内存等优势。没有答案,您需要单独分析每种情况。
因此,当我们计算中间结果时,我们还需要使用一些内存对吗? 有人可以向我解释吗?
计算机中有多个内存级别。 图层看起来像这样
当您使用现场计算的中间结果时,这些结果通常不会离开寄存器,或者可能只到达缓存,因此不受可用系统内存带宽的限制,也不受内存总线仲裁或地址生成互锁的阻止。
您的老师的建议过于复杂。
如果您认为“中间”是一个术语 (从单词的算术意义上来说),请问自己,您的代码是否在其他地方重用了该术语? 即,如果您有如下代码:
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
要比从内存位置检索它要有效得多。
我的建议是:
这伤了我。
问问你的老师(或者更好,不要,因为他在编程方面的能力我不信任他),他是否衡量过它以及有什么不同。 进行速度编程时的规则是:如果尚未测量速度,并且在更改前后都没有测量速度,那么您所做的就是纯粹基于假设并且毫无价值。
实际上,优化的编译器将采用您编写的代码,并将其转换为最快的机器代码。 结果,代码或速度上不可能有任何差异。
另一方面,使用中间变量将使复杂的表达式更容易理解和更容易理解,并且使调试变得更加容易。 如果庞大的复杂表达式给出的结果看起来像是错误的结果,则中间变量可以使您一点一点地检查计算并找出错误所在。
现在,即使他是正确的并删除中间变量也可以使您的代码更快,即使有人关心速度差异,他也会错了:提高代码的可读性和调试性会使您更快地获得正确工作的代码版本(如果它不起作用,那么没人会在乎它有多快。 现在,如果事实证明代码需要更快,那么您节省的时间将使您进行更改,从而使其真正更快。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.