我有很长的浮点正数列表(std::vector<float>
,大小〜1000)。数字以降序排序。如果我按照以下顺序对它们求和:
for (auto v : vec) { sum += v; }
我猜我可能会遇到一些数值稳定性问题,因为接近向量的结尾sum
将比更大v
。最简单的解决方案是以相反的顺序遍历向量。我的问题是:既有效率又有前瞻性吗?我会丢失更多的缓存吗?
还有其他智能解决方案吗?
我有很长的浮点正数列表(std::vector<float>
,大小〜1000)。数字以降序排序。如果我按照以下顺序对它们求和:
for (auto v : vec) { sum += v; }
我猜我可能会遇到一些数值稳定性问题,因为接近向量的结尾sum
将比更大v
。最简单的解决方案是以相反的顺序遍历向量。我的问题是:既有效率又有前瞻性吗?我会丢失更多的缓存吗?
还有其他智能解决方案吗?
Answers:
我想我可能会有一些数值稳定性问题
因此进行测试。当前,您有一个假设的问题,也就是说,完全没有问题。
如果您进行测试,并且假设变成了实际问题,那么您应该担心实际解决问题。
就是说-浮点精度可能会引起问题,但是您可以先确定浮点精度是否确实对您的数据有用,然后再将其优先于其他所有内容。
...我会丢失更多的缓存吗?
一千个浮点数是4Kb-它适合现代大众市场系统中的缓存(如果您有其他平台,请告诉我们它是什么)。
唯一的风险是预取器在向后迭代时不会为您提供帮助,但是您的向量当然可能已经在缓存中。除非在完整程序的上下文中进行概要分析,否则您无法真正确定这一点,因此,在拥有完整程序之前,不必担心它。
还有其他智能解决方案吗?
在实际成为问题之前,不必担心可能会成为问题的事物。最多值得一提的是可能存在的问题,并对代码进行结构化,以便以后可以用经过仔细优化的解决方案替换最简单的解决方案,而无需重新编写其他所有内容。
我对您的用例进行了基准测试,结果(参见附图)指向这样一个方向,即向前或向后循环不会对性能造成任何影响。
您可能还需要在硬件+编译器上进行度量。
使用STL执行求和,与手动循环数据一样快,但更具表现力。
使用以下内容进行反向累积:
std::accumulate(rbegin(data), rend(data), 0.0f);
而向前积累:
std::accumulate(begin(data), end(data), 0.0f);
state
循环中的部分是定时的。
最简单的解决方案是以相反的顺序遍历向量。我的问题是:既有效率又有前瞻性吗?我会丢失更多的缓存吗?
是的,它很有效。已调整硬件中的分支预测和智能缓存策略以进行顺序访问。您可以安全地累积向量:
#include <numeric>
auto const sum = std::accumulate(crbegin(v), crend(v), 0.f);
为此,您可以使用反向迭代器,而无需在其中进行任何转置std::vector<float> vec
:
float sum{0.f};
for (auto rIt = vec.rbegin(); rIt!= vec.rend(); ++rIt)
{
sum += *rit;
}
或使用标准算法完成相同的工作:
float sum = std::accumulate(vec.crbegin(), vec.crend(), 0.f);
性能必须相同,仅更改矢量的旁路方向
如果用数值稳定性表示精度,那么是的,您可能会遇到精度问题。根据最大值与最小值的比率以及您对结果准确性的要求,这可能是问题,也可能不是问题。
如果您确实想获得较高的精度,请考虑使用Kahan求和 -这会使用额外的浮点数来进行误差补偿。也有成对求和。
有关精度和时间之间折衷的详细分析,请参见本文。
C ++ 17的更新:
提到了其他一些答案std::accumulate
。从C ++ 17开始,有一些执行策略可以使算法并行化。
例如
#include <vector>
#include <execution>
#include <iostream>
#include <numeric>
int main()
{
std::vector<double> input{0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.4, 0.6, 0.5};
double reduceResult = std::reduce(std::execution::par, std::begin(input), std::end(input));
std:: cout << "reduceResult " << reduceResult << '\n';
}
这应该以不确定的舍入错误为代价使大型数据集的求和速度更快(我假设用户将无法确定线程分区)。