我正在编写一个2D曲线算法,并且有一些代码可以有效地进行求和a:
for (i=0, end=...; i<end; i++) {
value += coefficients[i] * expensiveToCalculateValue(i);
}
其中某些迭代步骤的值coefficients[i]
为零。由于零次仍然为零(至少在简单的算术规则下),我认为我可以通过首先检查coefficients[i]
是否为零来显着优化此代码,如果是,则只需continue
到下一次迭代。添加,排序,工作出色。
但这留下了一个问题:为什么这不适合我呢?这不是一些创造性的小众乘法版本,它是简单的算术。实际上所有语言都会使二进制OR和AND操作短路,如果找到一个操作数会使结果从该点开始不变,那么为什么算术乘以零不会同样短路呢?
我尝试用Java,PHP,JavaScript,Perl,Python,C ++运行这段代码(为synax修改),甚至看看Prolog做了什么,但没有人意识到一旦他们看到“零次...... “他们没有必要评估潜在的昂贵的第二(或第三,第四等)术语:
printed = 0;
function veryExpensive() {
print "oh god this costs so much, x" + (printed++);
return 0;
}
value = 0 * veryExpensive() * veryExpensive() * veryExpensive()
所有这些人最终都跑了veryExpensive()
三次。
现在,我知道你可以 - 如果你是那种人 - 根据你可以依赖它的事实编写你的veryExpensive
函数来执行管理开销工作尽管它的结果没有对算术表达做出贡献(如果你这样做,你可能会滥用一种语言,但是在编程生涯中,每个人都喜欢鬼鬼祟祟的忍者代码),但是你只会这样做因为你知道这种语言碰巧有趣的是没有针对这种情况进行优化。如果语言为您优化了算术评估,那么您的代码表达性就不会受到任何阻碍。
那么:是否有一些历史先例导致当前使用的大量语言优化为“真正的OR ......”和“虚假的......”而不是“零时间......”?为什么我们优化二进制操作,而不是MUL 0? (如果我们很幸运,有人会有一个很有趣的故事来说明为什么我们现在不会发生短路)
更新
John Skeet和Nik Bougalis都提出了很好的论据,说明为什么用现有语言优化这一问题会导致问题,但Nik的回答更多地问了这个问题,所以我将他的回答标记为“正确”。也就是说,它们涵盖了同一问题的不同方面,因此真正的答案是两者的结合。
答案 0 :(得分:9)
他们所有人最终都跑了三次非常昂贵()。
所以他们应该。
但你只是这样做,因为你知道这种语言恰好在这种情况下无法优化。
不,这不是“有趣”没有优化的问题。优化问题不违反语言规范。
如果语言指定操作X * Y
首先评估X
然后评估Y
,然后将这两个值相乘,那么它只是一个不正确的优化来删除{{1}的评估如果Y
的值恰好是0。
当然,做的运算符通常都是这样的 - 特别是(在C语言中):
X
,只评估 a ? b : c
或 b
c
只评估x || y
如果y
为假x
只评估x && y
如果y
为真 C#具有空合并运算符x
,仅当x ?? y
为空时才会评估y
。
乘法可以这样定义,但我怀疑:
x
和x | y
是非短路的那样)。基本上,我不认为所有参与者都有额外的复杂性。
答案 1 :(得分:2)
让编译器自动添加运行时检查可能或可能有意义。在您的特定情况下,检查可能确实提高了性能,但您的特定情况并非最终所有,通过这种情况判断优化。
如果乘法是非常昂贵的和,微处理器没有内部的乘法优化为零,和保证零乘法的结果(例如{{ 1}})为零然后检查可能有意义。但这是很多和操作数,如你所知,你可以短路和。
假设您有一个准随机的数字分布,以及一些零到非零数的未知比率。根据比率,零和非零序列的运行长度以及处理器的分支预测算法,检查实际上可能会引起麻烦(即管道停滞)。
你仍认为编译器应该代表你插入这样的支票吗?