【问题标题】:when calculate a^b why parallel not work but parallelStream could当计算 a^b 为什么并行不起作用但并行流可以
【发布时间】:2017-10-16 05:58:48
【问题描述】:

我想计算 a^b ,例如2^30,

public long pow(final int a, final int  b)

我首先使用这种方式

return LongStream.range(0, b).reduce(1, (acc, x) -> a * acc); // 1073741824

得到了正确的结果。然后我想并行计算,自然就改成了

return LongStream.range(0, b).parallel().reduce(1, (acc, x) -> a * acc); // 32

但在这种情况下,结果只是32。为什么?

所以为了支持并行我又改了

return Collections.nCopies(b,a).parallelStream().reduce(1, (acc, x) -> acc * x); // 1073741824

在这种情况下它可以工作。

那么parallel的方式有什么问题呢?

【问题讨论】:

    标签: java parallel-processing java-8 java-stream reduce


    【解决方案1】:

    reduce 要求提供的函数为 associative。您的函数(acc, x) -> a * acc 不满足要求,因此违反了合同。

    要具有关联性,函数必须满足 (x op y) op z == x op (y op z) 的任意 x、y 和 z。但是对于您的功能,(x op y) op z = x*a^2x op (y op z) = x * a

    此外,提供给 reduce 的第一个参数必须是关于累加器函数的标识。所以对于任何 x,1 op x == x 一定是真的。但这也不适用于您的累加器功能,因为1 op x == a

    正确的做法是:

    LongStream.range(0, b).map(x -> a).reduce(1, (u, v) -> u * v);
    

    无论流是并行的还是顺序的,这都能保证正常工作。

    【讨论】:

    • 我仍然很困惑,如果它不是关联的,为什么第一种方式 - 没有并行 - 它有效?我什至试图模拟这个结果—— 32 ,但我无法重现它。我的代码是gist.github.com/zhugw/248f2942ce955877a18a2f3f4fa4c68e 我认为并行是通过这种方式实现的。
    • 它恰好起作用,因为当前的顺序实现发生实际上并不依赖于两个要求(关联性和身份)中的任何一个。并行实现确实取决于两者。但不能保证您的第一个样本将继续有效。 java 标准库的替代实现,甚至 JDK 的未来版本,很可能会以破坏它的方式实现。
    • 嗨,我有一些新发现。请在下面查看我的答案。
    【解决方案2】:

    追查源码,终于知道为什么结果是32了。

    return LongStream.range(0, b).parallel().reduce(1, (acc, x) -> a * acc); // 32
    

    相关源码

       // java.util.stream.ReduceOps.ReduceTask#onCompletion
        @Override
        public void onCompletion(CountedCompleter<?> caller) {
            if (!isLeaf()) {
                S leftResult = leftChild.getLocalResult();
                leftResult.combine(rightChild.getLocalResult()); // here to combine
                setLocalResult(leftResult);
            }
    
        }
    
        // java.util.stream.TerminalOp
        @Override
        public void combine(ReducingSink other) {
            accept(other.state);
        }
        @Override
        public void accept(long t) {
            state = operator.applyAsLong(state, t); // (acc, x)
        }
    

    因为实际上没有在 lambda 中使用x

    (acc, x) -> a * acc;
    

    所以实际效果是这样的

    leftResult.combine(2); 
    

    Online demo 来模拟这种现象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-27
      • 1970-01-01
      • 1970-01-01
      • 2021-09-10
      • 1970-01-01
      • 2011-08-10
      • 2014-01-15
      • 2019-08-29
      相关资源
      最近更新 更多