在实践中,在运行时为零的乘法中是否有任何"lazy"评估

In practice, is there any "lazy" evalution in multiplication with zero in run time

本文关键字:是否 评估 任何 lazy 实践中 运行时      更新时间:2023-10-16

机器或编译器是否会使用零乘以整数必须始终为零的属性?考虑

int f(int c,int d,int e,int f){//这里有一些复杂的计算。。。}int foo(int a、int b、int c、int d、int e、int f){return(a-b)*f(c,d,e,f);}

在运行时,如果我们在a == b中传递一些参数,那么在数学上就不需要计算f()的结果(假设没有未定义或奇怪的行为)。结果必须始终为0。

我不太确定机器/编译器是否有可能使用一些优化技术来跳过f()的计算。或者,反过来问,是否保证无论ab的值是多少,都会始终调用f()

我用C和C++来标记这个问题,以避免在这种情况下C和C++的规则略有不同。如果是,请分别详细说明

更新感谢您的讨论。据我所知,函数副作用的可能存在肯定是一个需要考虑的因素。然而,我想澄清的是,在我的意图中,助手函数f()不是必须的。类似的代码

int foo(int a,int b,int c,int d,int e,int f){return(a-b)*/*一些c,d,e,f*/的复杂表达式;}

也符合资格。抱歉一开始没有说清楚。

编译器

如果算术计算由常量组成,编译器通常(意味着大多数编译器)会优化算术计算。例如,i = 1 + 3;将被优化为i = 4;,但计算越复杂,能够优化它的编译器就越少。编译器通常使用树结构递归工作,并搜索它们以找到可能的优化。因此,如果添加2或20个常量,没有什么区别,但如果添加在循环中,则会有区别。在这种情况下,调用foo(a, a, x, y, z);比调用foo(1, 1, x, y, z);更不可能被优化。

如果编译器首先内联小函数,然后搜索算术优化,那么如果参数是在编译时确定的,那么编译器很可能能够优化掉所有额外的指令。归根结底,编译器能在不运行程序的情况下确保foo的结果为0吗?

需要注意的两件事:

  • 编译器可以选择性地优化不同的东西(对于使用-O0、-O1、-O2、-O3和其他更具体的命令的gcc)

  • 编译器本身就是编写的程序,而不是一个神奇的黑盒。为了让编译器优化foo,开发人员必须在其中的某个地方写:检查减法是否与同一个变量有关,如果是,则用0替换结果。在附近的某个地方:检查你是否用零乘,并用0代替。所有这些都是在编译时完成的。

对于要在运行时优化的编译器,生成的程序集将包含对每个变量的检查,以检查任何一个零,而不是将a和b相乘。我不认为任何编译器能做到这一点。

处理器

处理器在很大程度上是一台愚蠢的机器,它完全按照自己的指令行事。乘法是由携带一些按位逻辑的硬件完成的。为了让处理器优化这个乘法运算,进行计算的电路还必须有一个部分,上面写着:如果其中一个被乘数为0,那么结果为0,我们不需要进行乘法运算。如果有人对处理器进行了编程,那么它就会被优化。这取决于实施,但我认为这不太可能。

这需要编译器生成分支,而它通常不喜欢这样做。如果你想要分支,请明确:

int f(int c, int d, int e, int f)
{
// some complex calculations here...
}
int foo(int a, int b, int c, int d, int e, int f)
{
if (a == b) return 0;
return (a - b) * f(c, d, e, f);
}

如在评论中所指出的,f也可能具有副作用,在这种情况下,保证不会是"副作用";优化的";离开

除非编译器能够在编译时确定(a - b)的求值结果始终为0,否则它不会尝试添加代码来在运行时执行求值。

主要原因是其他答案中已经讨论过的:提供其中一个操作数的函数可能会产生副作用,而且你通常不想避免这种情况发生(如果你想要,你必须自己添加求值)。

另一个原因是硬件已经做到了,其中一个操作数为0的乘法通常比常规乘法占用更少的周期。

注意,这与条件表达式if ( (a - b) == 0 || f(c, d, e, f) == 0 )中的短路评估不同。在这种情况下,第一个条件可以避免第二个条件在运行时执行f()