我正在编写一个算法来查找nxn矩阵的逆矩阵。让我们来看一个3x3矩阵的具体情况。
当您手动反转矩阵时,通常会查找包含一个或多个零的行/列,以便更快地执行行列式计算,因为它会消除您需要计算的项。
在C / C ++中遵循此逻辑,如果您标识具有一个或多个零的行/列,您将最终得到以下代码:
float term1 = currentElement * DetOf2x2(...);
// ^
// This is equal to 0.
//
// float term2 = ... and so on.
由于编译器无法知道currentElement
在编译时将为零,因此无法将其优化为float term = 0;
,因此浮点乘法将在运行时执行。
我的问题是,这些零值是否会使浮点乘法更快,或者无论currentElement
的值如何,乘法都会花费相同的时间?如果在运行时无法优化乘法,那么我可以删除搜索包含零的行/列的逻辑。
答案 0 :(得分:7)
除非计算是trival(例如所有常量),否则不允许编译器对此进行优化。
原因是,DetOf2x2可能返回NAN浮点值。将NAN与零相乘不会返回零,而是再次返回NAN。
您可以在此处使用此小测试自己尝试:
int main (int argc, char **args)
{
// generate a NAN
float a = sqrt (-1);
// Multiply NAN with zero..
float b = 0*a;
// this should *not* output zero
printf ("%f\n", b);
}
如果要优化代码,则必须自行测试零。编译器不会为你做那件事。
答案 1 :(得分:6)
float term1 = currentElement * DetOf2x2(...);
即使currentElement为0,编译器也会调用DetOf2x2(...)
:这肯定比最终的乘法要贵得多,无论是否为0。原因有很多:
DetOf2x2(...)
可能会产生副作用(例如输出到日志文件),即使currentElement
为0
,也可能会发生DetOf2x2(...)
可能会返回非传播号码/ NaN标记的值,无论如何都会传播到term1
(如Nils Pipenbrinck首先所述)鉴于DetOf2x2(...)
几乎肯定会处理只能在运行时确定的值,后一种可能性不能在编译时排除。
如果您想避免拨打Detof2x2(...)
,请尝试:
float term1 = (currentElement != 0) ? currentElement * DetOf2x2(...) : 0;
答案 2 :(得分:2)
现代CPU实际上会比一般的乘法更快,更快地处理乘数非常,并且比分支更快地很多。甚至不打算尝试优化它,除非零将通过至少几十个指令传播。
答案 3 :(得分:0)
在运行时执行的优化称为JIT(即时)优化。在翻译(编译)时执行的优化称为AOT(提前)优化。你指的是JIT优化。编译器可能会在您的机器代码中引入JIT优化,但它实现的优化要比常见的AOT优化要复杂得多。优化通常基于重要性来实现,并且这种“优化”可能被视为负面地影响其他算法。 C实现不需要执行任何这些优化。
您可以手动提供优化,这将是“搜索包含零的行/列的逻辑”,或者类似这样的内容:float term1 = currentElement != 0 ? currentElement * DetOf2x2(...) : 0;
答案 4 :(得分:0)
以下构造在编译时可以猜出" currentElement"的值。
float term1 = currentElement? currentElement * DetOf2x2(...):0;
如果在编译时无法猜到,它将在运行时进行检查,性能取决于处理器体系结构:分支之间的权衡(包括分支延迟和重建指令管道的延迟可能会增加到10或20个周期)和平坦代码(一些处理器每个周期运行3个指令)和硬件分支预测(当硬件支持分支预测时)。
由于x86_64处理器上的乘法吞吐量接近1个周期,因此根据操作数值(如0.0,1.0,2.0或12345678.99)不存在性能差异。如果存在这样的差异,那将被视为加密风格软件中的隐蔽通道。
GCC允许在编译时检查函数参数
内联浮动myFn(float currentElement,myMatrix M)
{
#if __builtin_constant_p(currentElement)&& currentElement == 0.0
返回0.0;
的#else
return currentElement * det(M);
#ENDIF
}
您需要在编译器中启用内联和过程间优化。