C++中双减法的优化
Posted
技术标签:
【中文标题】C++中双减法的优化【英文标题】:Optimization of double subtraction in C++ 【发布时间】:2012-02-14 19:28:56 【问题描述】:我有以下代码用于计算两个向量之间的距离:
double dist(vector<double> & vecA, vector<double> & vecB)
double curDist = 0.0;
for (size_t i = 0; i < vecA.size(); i++)
double dif = vecA[i] - vecB[i];
curDist += dif * dif;
return curDist;
这个函数是我的应用程序的一个主要瓶颈,因为它依赖于大量的距离计算,在典型输入上消耗超过 60% 的 CPU 时间。此外,以下行:
double dif = vecA[i] - vecB[i];
在这个函数中负责超过 77% 的 CPU 时间。我的问题是:是否有可能以某种方式优化此功能?
注意事项:
为了分析我的应用程序,我使用了 Intel Amplifier XE; 减少距离计算的数量不是一个可行的解决方案 我;【问题讨论】:
如果该行确实占用了超过一半的 CPU 时间,那么它似乎与缓存相关(即第一次访问每个元素都会受到缓存命中),并且很难通过加速代码进行优化。您可能需要研究基于向量的指令以减轻一点(a'la SSE3) @Mystical vector.size() 根据应用程序输入(即数据集维度)在 4 到 100 之间变化。 这是 STL 的向量吗?如果这里的所有人都假设它是而事实并非如此,那将是一种耻辱。 @Mooing duck - 它还计算表示为坐标向量的两点之间距离的平方。 您是否单步执行循环,一次一条指令,以确保它没有调用隐藏函数或边界检查? 【参考方案1】:我现在能想到两个可能的问题:
此计算受内存限制。curDist
存在迭代到迭代的依赖关系。
此计算受内存限制。
您的数据集大于 CPU 缓存。因此,在这种情况下,除非您可以重构算法,否则再多的优化都无济于事。
curDist
存在迭代到迭代的依赖关系。
您依赖于curDist
。这将阻止编译器进行向量化。 (另外,不要总是相信分析器编号。它们可能不准确,尤其是在编译器优化之后。)
通常,编译器矢量化程序可以将curDist
拆分为多个部分和,以展开/矢量化循环。但它不能在严格的浮点行为下做到这一点。如果您还没有,您可以尝试放松您的浮点模式。或者您可以拆分总和并自行展开。
例如,这种优化是编译器可以用整数做的,但不一定用浮点:
double curDist0 = 0.0;
double curDist1 = 0.0;
double curDist2 = 0.0;
double curDist3 = 0.0;
for (size_t i = 0; i < vecA.size() - 3; i += 4)
double dif0 = vecA[i + 0] - vecB[i + 0];
double dif1 = vecA[i + 1] - vecB[i + 1];
double dif2 = vecA[i + 2] - vecB[i + 2];
double dif3 = vecA[i + 3] - vecB[i + 3];
curDist0 += dif0 * dif0;
curDist1 += dif1 * dif1;
curDist2 += dif2 * dif2;
curDist3 += dif3 * dif3;
// Do some sort of cleanup in case (vecA.size() % 4 != 0)
double curDist = curDist0 + curDist1 + curDist2 + curDist3;
【讨论】:
我看到您使用的是 Visual Studio C++ 2010。该编译器还不支持矢量化...您可能会使用 ICC 或 GCC 获得更好的性能。 可能想在那里检查你的 for 循环... ;) @Nim 感谢您的关注。 :) 好答案!您需要 i+=4,然后从展开的左侧开始添加最后(最多 3 步)的原始循环。 @MooingDuck 我通常只是复制原始循环。但在我几乎所有的应用程序中,我都可以强制数据集为 2 的幂的倍数,所以我一开始就没有这个问题。要真正高效,您需要一些启动代码来对齐数据,然后再进入完全展开+矢量化的循环。【参考方案2】:您可以在循环的每次迭代中消除对vecA.size()
的调用,只需在循环之前调用一次即可。您还可以进行循环展开,以便在每次循环迭代时给自己更多的计算量。您使用的是什么编译器,以及什么优化设置?编译器通常会为您展开,但您可以手动完成。
【讨论】:
只用片刻就击败我到vecA.size()
。编译器可能无法确定该值在循环期间是否保持不变,因此它可能无法对其进行优化。循环展开是另一个好主意。
其实vector::size
是一个常数时间的操作,编译器应该可以内联并优化掉...
我正在使用带有默认参数的 Visual Studio C++ 2010。请注意,分析器的结果显示 vecA.size() 不会占用大量 CPU 时间。
“恒定时间”并不意味着它与可能位于寄存器中的局部变量访问一样快。编译器不能保证 vector::size 在循环中保持不变。但是如果你有一个本地变量,你把大小放进去,编译器就会知道这个值不能改变
@TJD 你是对的:消除对 vecA.size() 的调用确实减少了整体 CPU 时间。现在我的下一步是解决减法线问题,这是主要的瓶颈。【参考方案3】:
如果可行(如果数字范围不大),您可能想探索使用定点来存储这些数字,而不是双精度数。
固定点会将这些转换为 int 操作而不是 double 操作。
另一个有趣的事情是,假设您的个人资料正确,查找似乎是一个重要因素(否则乘法可能比减法更昂贵)。
我会尝试使用 const 向量迭代器而不是随机访问查找。它可能在两个方面有所帮助:1 - 它是恒定的,2 - 迭代器的串行特性可以让处理器进行更好的缓存。
【讨论】:
您要么需要使用库,要么自己实现它。如果你搜索它,就会有很多关于 *** 的信息。【参考方案4】:如果您的平台没有(或未使用)支持浮点数学的 ALU,则浮点库本质上速度很慢,并且会消耗额外的非易失性内存。我建议改用 32 位 (long
) 或 64 位 (long long
) 定点算法。然后在算法结束时将最终结果转换为浮点数。几年前我在一个项目中做了这个,以提高 I2T 算法的性能,并且效果很好。
【讨论】:
以上是关于C++中双减法的优化的主要内容,如果未能解决你的问题,请参考以下文章
关于 国产麒麟系统中双精度double除法编译优化导商变量不变化(代码调整+volatile) 的解决方法