有序数的有效稳定和


12

我有很长的浮点正数列表(std::vector<float>,大小〜1000)。数字以降序排序。如果我按照以下顺序对它们求和:

for (auto v : vec) { sum += v; }

我猜我可能会遇到一些数值稳定性问题,因为接近向量的结尾sum将比更大v。最简单的解决方案是以相反的顺序遍历向量。我的问题是:既有效率又有前瞻性吗?我会丢失更多的缓存吗?

还有其他智能解决方案吗?


1
速度问题很容易回答。进行基准测试。
Davide Spataro,

速度比准确性重要吗?
形成鲜明

不是完全重复,而是非常相似的问题:使用浮点数的序列求和
acraig5075 '19

4
您可能需要注意负数。
AProgrammer19年

3
如果您实际上在乎高精度,请查看Kahan summation
Max Langhof

Answers:


3

我想我可能会有一些数值稳定性问题

因此进行测试。当前,您有一个假设的问题,也就是说,完全没有问题。

如果您进行测试,并且假设变成了实际问题,那么您应该担心实际解决问题。

就是说-浮点精度可能会引起问题,但是您可以先确定浮点精度是否确实对您的数据有用,然后再将其优先于其他所有内容。

...我会丢失更多的缓存吗?

一千个浮点数是4Kb-它适合现代大众市场系统中的缓存(如果您有其他平台,请告诉我们它是什么)。

唯一的风险是预取器在向后迭代时不会为您提供帮助,但是您的向量当然可能已经在缓存中。除非在完整程序的上下文中进行概要分析,否则您无法真正确定这一点,因此,在拥有完整程序之前,不必担心它。

还有其他智能解决方案吗?

在实际成为问题之前,不必担心可能会成为问题的事物。最多值得一提的是可能存在的问题,并对代码进行结构化,以便以后可以用经过仔细优化的解决方案替换最简单的解决方案,而无需重新编写其他所有内容。


5

您的用例进行了基准测试,结果(参见附图)指向这样一个方向,即向前或向后循环不会对性能造成任何影响。

您可能还需要在硬件+编译器上进行度量。


使用STL执行求和,与手动循环数据一样快,但更具表现力。

使用以下内容进行反向累积:

std::accumulate(rbegin(data), rend(data), 0.0f);

而向前积累:

std::accumulate(begin(data), end(data), 0.0f);

在此处输入图片说明


该网站非常酷。只是要确保:您没有定时随机生成,对吗?
Ruggero Turra,

不,只有state循环中的部分是定时的。
Davide Spataro,

2

最简单的解决方案是以相反的顺序遍历向量。我的问题是:既有效率又有前瞻性吗?我会丢失更多的缓存吗?

是的,它很有效。已调整硬件中的分支预测和智能缓存策略以进行顺序访问。您可以安全地累积向量:

#include <numeric>

auto const sum = std::accumulate(crbegin(v), crend(v), 0.f);

2
您能否澄清:在这种情况下,“顺序访问”是指前进,后退,或两者兼而有之?
Ruggero Turra

1
@RuggeroTurra我无法找到一个来源,除非我现在不想阅读CPU数据表。
YSC

@RuggeroTurra通常,顺序访问将意味着前进。所有半体面的内存预取器均捕获向前的顺序访问。
牙刷,

@牙刷,谢谢。因此,如果我倒退,原则上可能是性能问题
Ruggero Turra,

原则上,至少在某些硬件上,如果整个向量尚未位于L1缓存中。
没用的

2

为此,您可以使用反向迭代器,而无需在其中进行任何转置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);

性能必须相同,仅更改矢量的旁路方向


如果我错了,请纠正我,但是我认为这比OP正在使用的foreach语句更有效,因为它会带来开销。YSC关于数值稳定性部分tho是正确的。
Sephiroth

4
@sephiroth不,任何一个像样的编译器都不会真正在乎是编写范围还是迭代器。
Max Langhof

1
由于存在缓存/预取功能,因此绝对不能保证实际性能是相同的。OP对此保持警惕是合理的。
Max Langhof

1

如果用数值稳定性表示精度,那么是的,您可能会遇到精度问题。根据最大值与最小值的比率以及您对结果准确性的要求,这可能是问题,也可能不是问题。

如果您确实想获得较高的精度,请考虑使用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';
}

这应该以不确定的舍入错误为代价使大型数据集的求和速度更快(我假设用户将无法确定线程分区)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.