【发布时间】:2014-06-28 03:56:44
【问题描述】:
我很好奇 Java 8 中的以下构造:
double[] doubles = //...
double sum = DoubleStream.of(doubles).parallel().sum();
切入正题:
-
sum的值是否始终相同,例如什么时候在不同的计算机上运行?
更多背景...
浮点算术是有损的并且(与实值算术不同)不是关联的。因此,除非注意工作的划分和重组方式,否则可能会导致不确定的结果。
我很高兴地发现 sum() 方法在底层使用了 Kahan Summation。这显着减少了错误,但仍然不能给出精确的*结果。
在我的测试中,重复调用似乎每次都返回相同的结果,但我想知道我们可以安全地假设它有多稳定。例如:
- 在所有情况下都稳定吗?
- 在具有相同内核数的计算机上是否稳定?
- 仅在给定计算机上稳定?
- 不能完全依赖它的稳定性吗?
我很高兴假设每台计算机上的 JVM 版本相同。
这是我做的一个测试:
public static void main(String[] args) {
Random random = new Random(42L);
for (int j = 1; j < 20; j++) {
// Stream increases in size and the magnitude of the values at each iteration.
double[] doubles = generate(random, j*100, j);
// Like a simple for loop
double sum1 = DoubleStream.of(doubles).reduce(0, Double::sum);
double sum2 = DoubleStream.of(doubles).sum();
double sum3 = DoubleStream.of(doubles).parallel().sum();
System.out.println(printStats(doubles, sum1, sum2, sum3));
// Is the parallel computation stable?
for (int i = 0; i < 1000; i++) {
double sum4 = DoubleStream.of(doubles).parallel().sum();
assert sum4 == sum3;
}
Arrays.sort(doubles);
}
}
/**
* @param spread When odd, returns a mix of +ve and -ve numbers.
* When even, returns only +ve numbers.
* Higher values cause a wider spread of magnitudes in the returned values.
* Must not be negative.
*/
private static double[] generate(Random random, int count, int spread) {
return random.doubles(count).map(x -> Math.pow(4*x-2, spread)).toArray();
}
private static String printStats(double[] doubles, double sum1, double sum2, double sum3) {
DoubleSummaryStatistics stats = DoubleStream.of(doubles).summaryStatistics();
return String.format("-----%nMin: %g, Max: %g, Average: %g%n"
+ "Serial difference: %g%n"
+ "Parallel difference: %g",
stats.getMin(), stats.getMax(), stats.getAverage(), sum2-sum1, sum3-sum1);
}
当我运行它时,前几次迭代是:
-----
Min: -1.89188, Max: 1.90414, Average: 0.0541140
Serial difference: -2.66454e-15
Parallel difference: -2.66454e-15
-----
Min: 0.000113827, Max: 3.99513, Average: 1.17402
Serial difference: 1.70530e-13
Parallel difference: 1.42109e-13
-----
Min: -7.95673, Max: 7.87757, Average: 0.0658356
Serial difference: 0.00000
Parallel difference: -7.10543e-15
-----
Min: 2.53794e-09, Max: 15.8122, Average: 2.96504
Serial difference: -4.54747e-13
Parallel difference: -6.82121e-13
请注意,虽然 sum2 和 sum3 可以被认为比 sum1 更准确 - 它们可能彼此不同!
我将Random 播种为 42,所以如果有人得到与我不同的结果,那将立即证明一些事情。 :-)
* 对于好奇的人......
- 这里是some (python) algorithms,可以提供精确的结果
- 具有我听说过的最佳性能特征的精确和算法是given here(ACM 订阅或需要付费)。每个输入需要 5 次触发器,但是(用 C 语言编写)是为了利用指令级并行性,并且只比简单求和慢 2 到 3 倍,这对于精确的结果来说听起来相当不错。 (c.f. Kahan summation at 4 flops per input)
【问题讨论】:
-
+1 表示一个写得很好的、有趣的问题,其中包含一个测试用例! (现在很少有这样的问题......)
-
我希望答案是“不,根本不要指望稳定。”
-
我认为DoubleStream::sum 的文档非常清楚地说明了这个问题:“浮点和的值是输入值和顺序的函数 加法运算。有意不定义此方法的加法运算顺序,以允许实现灵活性以提高计算结果的速度和准确性。"
-
@nosid 是的,我认为这就是它的全部内容,并且很乐意接受作为答案发布的内容。我应该更仔细地重新阅读那段 :-) 纯粹出于我的好奇心,如果有人能对当前的实现再多说一点,我仍然很感兴趣。是什么让它在我的测试中稳定? (我实际上另有预期)。它在多 CPU 架构上会稳定吗? (我只是多核。)
-
您最大的数据集是 2k 浮点数,假设实现相当聪明,它不会为该大小启动任何线程。同步等(即使假设现有线程池)会比我认为的单线程实现更昂贵(8 dp flops 意味着如果最佳,我们将在这里花费
标签: java multithreading java-8 numeric java-stream