Java 8流矩阵乘法比For循环慢10倍?
我创建了一个使用流执行矩阵乘法的模块。可以在这里找到: 我试图编写一个基准测试,以便将流循环实现与Apache Commons Math中相应的for循环实现进行比较 基准模块如下所示: 这里的实际基准是: 当我在大小为100X100和1000X1000的矩阵上运行基准测试时,结果表明Apache Commons Math(使用for循环)比相应的流实现快10倍(大致)Java 8流矩阵乘法比For循环慢10倍?,java,math,java-8,benchmarking,java-stream,Java,Math,Java 8,Benchmarking,Java Stream,我创建了一个使用流执行矩阵乘法的模块。可以在这里找到: 我试图编写一个基准测试,以便将流循环实现与Apache Commons Math中相应的for循环实现进行比较 基准模块如下所示: 这里的实际基准是: 当我在大小为100X100和1000X1000的矩阵上运行基准测试时,结果表明Apache Commons Math(使用for循环)比相应的流实现快10倍(大致) # Run complete. Total time: 00:14:10 Benchmark
# Run complete. Total time: 00:14:10
Benchmark Mode Cnt Score Error Units
MultiplyBenchmark.multiplyCM1000_1000 avgt 30 1040.804 ± 11.796 ms/op
MultiplyBenchmark.multiplyCM100_100 avgt 30 0.790 ± 0.010 ms/op
MultiplyBenchmark.multiplyFM1000_1000 avgt 30 11981.228 ± 405.812 ms/op
MultiplyBenchmark.multiplyFM100_100 avgt 30 7.224 ± 0.685 ms/op
我在基准测试中是否做错了什么(希望:)
我添加了经过测试的方法,这样每个人都可以看到正在比较的内容。这是Apache Commons Math Array2DroArralMatrix.multiply()方法:
/**
*返回将{@code this}乘以{@code m}后的结果。
*
*@param m m矩阵通过后乘
*@return{@code this*m}
*@抛出维度不匹配异常,如果
*{@code columnDimension(this)!=行维度(m)}
*/
公共ARRAY2DROREALMATRIX乘法(最终ARRAY2DROREALMATRIX m)
抛出维度不匹配异常{
MatrixUtils.checkMultiplicationCompatible(this,m);
final int nRows=this.getRowDimension();
final int nCols=m.getColumnDimension();
final int nSum=this.getColumnDimension();
最终双精度[][]输出数据=新双精度[nRows][nCols];
//将包含一列“m”。
最终双精度[]mCol=新双精度[nSum];
最终双精度[][]mData=m.data;
//倍增。
for(int col=0;col
这是相应的流实现:
/**
* Returns a {@link BinaryOperator} that multiplies {@link SimpleMatrix}
* {@code m1} times {@link SimpleMatrix} {@code m2} (m1 X m2).
*
* Example {@code multiply(true).apply(m1, m2);}
*
* @param parallel
* Whether to perform the operation concurrently.
*
* @throws MathException
* Of type {@code MATRIX_DIMENSION_MISMATCH__MULTIPLICATION} if
* {@code m} is not the same size as {@code this}.
*
* @return the {@link BinaryOperator} that performs the operation.
*/
public static BinaryOperator<SimpleMatrix> multiply(boolean parallel) {
return (m1, m2) -> {
checkMultiplicationCompatible(m1, m2);
double[][] a1 = m1.toArray();
double[][] a2 = m2.toArray();
Stream<double[]> stream = Arrays.stream(a1);
stream = parallel ? stream.parallel() : stream;
final double[][] result =
stream.map(r -> range(0, a2[0].length)
.mapToDouble(i -> range(0, a2.length).mapToDouble(j -> r[j]
* a2[j][i]).sum())
.toArray()).toArray(double[][]::new);
return new SimpleMatrix(result);
};
}
/**
*返回一个{@link BinaryOperator}乘以{@link SimpleMatrix}
*{@code m1}乘以{@link SimpleMatrix}{@code m2}(m1×m2)。
*
*示例{@code multiply(true).apply(m1,m2);}
*
*@param并行
*是否同时执行该操作。
*
*@抛出异常
*类型{@code MATRIX\u DIMENSION\u MISMATCH\u乘法}如果
*{@code m}与{@code this}的大小不同。
*
*@return执行该操作的{@link BinaryOperator}。
*/
公共静态二进制运算符乘法(布尔并行){
返回(m1,m2)->{
checkMultiplicationCompatible(m1,m2);
double[]a1=m1.toArray();
double[]a2=m2.toArray();
Stream=Arrays.Stream(a1);
stream=parallel?stream.parallel():stream;
最终双[][]结果=
stream.map(r->range(0,a2[0].长度)
.maptouble(i->range(0,a2.长度).maptouble(j->r[j]
*a2[j][i]).sum())
.toArray()).toArray(双[]]::新建);
返回新的SimpleMatrix(结果);
};
}
蒂亚,
Ole查看一下双管道。toArray:
public final double[] toArray() {
return Nodes.flattenDouble((Node.OfDouble) evaluateToArrayNode(Double[]::new))
.asPrimitiveArray();
}
似乎首先创建一个装箱数组,然后将其转换为一个基本数组。@Holger
toArray
是一个简单的字段访问器。我明白了。我的猜测是数据局部性和缓存未命中-可能还有更多…我发现结果仍然很奇怪。只要有可能,我会尽快运行该基准测试。@assylias:DoubleStream.sum()
使用的错误补偿算法可能比简单的求和循环更昂贵。然而,我并不期望因子10。关于局部性,与Apache的数学库不同,您的循环变量对提高数据局部性没有任何作用。@Holger发现得很好-不使用DoubleStream::sum
将性能提高了30%-现在比率为“仅”与原始版本相比,速度慢了6倍,慢了8倍。@assylias:通过将数组长度读入局部变量,可以获得更多的百分比,就像在循环变量中一样,也可以a2
(与lambda实例通过捕获的this
实例重新读取字段相比,hotspot更容易消除循环变量中对a2
字段的不必要的重新读取)。似乎此函数仅用于实现内部接口(或者可能在调用boxed()
时使用)。在执行逐步调试时,您将看到此生成器未在本例中使用。返回的节点将包含一个double[]
数组,并且由于此流具有固定大小,asPrimitiveArray()
将直接返回它。@霍尔格感谢您的更新。如果答案错误,我将查看它并删除它。可能是由于当时创建了大量流造成的。答案实际上是不正确的。之后不再使用此生成器。它已通过AP::evaluateToaryNode
->AP::evaluate代码>->DP::makeNodeBuilder
在这里它被忽略。
public final double[] toArray() {
return Nodes.flattenDouble((Node.OfDouble) evaluateToArrayNode(Double[]::new))
.asPrimitiveArray();
}