Java 用于转置双[]矩阵的压缩流表达式
我想用最紧凑、最有效的表达式转换一个Java 用于转置双[]矩阵的压缩流表达式,java,math,java-8,java-stream,Java,Math,Java 8,Java Stream,我想用最紧凑、最有效的表达式转换一个double[][]矩阵。现在我有这个: public static Function<double[][], double[][]> transpose() { return (m) -> { final int rows = m.length; final int columns = m[0].length; double[][] transpose = new double[col
double[][]
矩阵。现在我有这个:
public static Function<double[][], double[][]> transpose() {
return (m) -> {
final int rows = m.length;
final int columns = m[0].length;
double[][] transpose = new double[columns][rows];
range(0, rows).forEach(r -> {
range(0, columns).forEach(c -> {
transpose[c][r] = m[r][c];
});
});
return transpose;
};
}
公共静态函数转置(){
返回(m)->{
最终整数行=m.length;
最终int列=m[0]。长度;
double[][]转置=新的双[列][行];
范围(0,行)。forEach(r->{
范围(0,列)。forEach(c->{
转置[c][r]=m[r][c];
});
});
返回转置;
};
}
想法?紧凑且效率更高:
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
transpose[c][r] = m[r][c];
for(int r=0;r
请注意,如果您有一个矩阵
类,该类包含一个双[][]
,另一个选项是返回一个具有相同基础数组但交换列/行索引的视图。复制时可以节省,但由于缓存位置更差,迭代时的性能可能会更差。您可以:
public static UnaryOperator<double[][]> transpose() {
return m -> {
return range(0, m[0].length).mapToObj(r ->
range(0, m.length).mapToDouble(c -> m[c][r]).toArray()
).toArray(double[][]::new);
};
}
我已经实现了一个JMH基准测试,比较了上面的代码、for循环版本和上面并行运行的代码。这三种方法都使用大小分别为100、1000和3000的随机平方矩阵调用。结果是,对于较小的矩阵,循环版本的
更快,但对于较大的矩阵,并行流解决方案的性能确实更好(Windows 10,JDK 1.8.0_66,i5-3230M@2.60 GHz):
基准代码:
@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(3)
public class StreamTest {
private static final UnaryOperator<double[][]> streamTranspose() {
return m -> {
return range(0, m[0].length).mapToObj(r ->
range(0, m.length).mapToDouble(c -> m[c][r]).toArray()
).toArray(double[][]::new);
};
}
private static final UnaryOperator<double[][]> parallelStreamTranspose() {
return m -> {
return range(0, m[0].length).parallel().mapToObj(r ->
range(0, m.length).parallel().mapToDouble(c -> m[c][r]).toArray()
).toArray(double[][]::new);
};
}
private static final Function<double[][], double[][]> forLoopTranspose() {
return m -> {
final int rows = m.length;
final int columns = m[0].length;
double[][] transpose = new double[columns][rows];
for (int r = 0; r < rows; r++)
for (int c = 0; c < columns; c++)
transpose[c][r] = m[r][c];
return transpose;
};
}
@State(Scope.Benchmark)
public static class MatrixContainer {
@Param({ "100", "1000", "3000" })
private int matrixSize;
private double[][] matrix;
@Setup(Level.Iteration)
public void setUp() {
ThreadLocalRandom random = ThreadLocalRandom.current();
matrix = random.doubles(matrixSize).mapToObj(i -> random.doubles(matrixSize).toArray()).toArray(double[][]::new);
}
}
@Benchmark
public double[][] streamTranspose(MatrixContainer c) {
return streamTranspose().apply(c.matrix);
}
@Benchmark
public double[][] parallelStreamTranspose(MatrixContainer c) {
return parallelStreamTranspose().apply(c.matrix);
}
@Benchmark
public double[][] forLoopTranspose(MatrixContainer c) {
return forLoopTranspose().apply(c.matrix);
}
}
@Warmup(迭代次数=10,时间=500,时间单位=timeUnit.ms)
@测量(迭代次数=10,时间=500,时间单位=时间单位。毫秒)
@基准模式(模式平均时间)
@OutputTimeUnit(时间单位毫秒)
@叉子(3)
公共类流测试{
私有静态最终一元运算符streamTranspose(){
返回m->{
返回范围(0,m[0]。长度)。mapToObj(r->
范围(0,m.length).mapToDouble(c->m[c][r]).toArray()
).toArray(双[]]::新);
};
}
私有静态最终一元运算符parallelStreamTranspose(){
返回m->{
返回范围(0,m[0].长度).parallel().mapToObj(r->
范围(0,m.length).parallel().mapToDouble(c->m[c][r]).toArray()
).toArray(双[]]::新);
};
}
LoopTranspose()的专用静态最终函数{
返回m->{
最终整数行=m.length;
最终int列=m[0]。长度;
double[][]转置=新的双[列][行];
对于(int r=0;rrandom.doubles(matrixSize).toArray()).toArray(double[][]::new);
}
}
@基准
公共双[][]流转置(MatrixContainer c){
返回streamTranspose().apply(c.matrix);
}
@基准
公共双[][]并行流转置(MatrixContainer c){
返回parallelStreamTranspose().apply(c.matrix);
}
@基准
公共双[][]forLoopTranspose(MatrixContainer c){
返回forLoopTranspose().apply(c.matrix);
}
}
如果您假设一个矩形输入(正如您的原始代码所依赖的那样),您可以将其编写为
public static Function<double[][], double[][]> transpose() {
return m -> range(0, m[0].length)
.mapToObj(c->range(0, m.length).mapToDouble(r->m[r][c]).toArray())
.toArray(double[][]::new);
}
公共静态函数转置(){
返回m->range(0,m[0].长度)
.mapToObj(c->range(0,m.length).maptoble(r->m[r][c]).toArray()
.toArray(双[]]::新);
}
这可以并行运行,但我想你需要一个非常大的矩阵才能从中获益。我的建议:对于简单的低级数学,你应该使用纯旧for循环,而不是Stream API。您还应该非常仔细地对这些代码进行基准测试
至于@Tunaki基准。首先,不应将单个测量限制为1微秒。matrixSize=100
的结果完全是垃圾:0093±0054
和0237±0134
:误差超过50%。请注意,在每次迭代之前和之后执行的时间测量并不是一个魔术,也需要时间。这样一个小的时间间隔很容易被一些Windows服务破坏,这些服务突然醒来,花了一些CPU周期来检查一些东西,然后再次进入睡眠状态。我通常将每次热身/测量时间设置为500毫秒,这个数字对我来说很舒服
第二,当使用非常简单的负载(例如将数字复制到基元数组)测试流API时,您应该始终使用类型配置文件污染进行测试,因为它确实很重要。在clean benchmark中,JIT编译器可以将所有内容内联到单个方法中,因为它知道,例如,在某个范围之后,您总是使用相同的lambda表达式调用相同的mapToObj
。但在实际应用中,情况并不相同。我通过以下方式修改了MatrixContainer
类:
@State(Scope.Benchmark)
public static class MatrixContainer {
@Param({"true", "false"})
private boolean pollute;
@Param({ "100", "1000", "3000" })
private int matrixSize;
private double[][] matrix;
@Setup(Level.Iteration)
public void setUp() {
ThreadLocalRandom random = ThreadLocalRandom.current();
matrix = random.doubles(matrixSize)
.mapToObj(i -> random.doubles(matrixSize).toArray())
.toArray(double[][]::new);
if(!pollute) return;
// do some seemingly harmless operations which will
// poison JIT compiler type profile with some other lambdas
for(int i=0; i<100; i++) {
range(0, 1000).map(x -> x+2).toArray();
range(0, 1000).map(x -> x+5).toArray();
range(0, 1000).mapToObj(x -> x*2).toArray();
range(0, 1000).mapToObj(x -> x*3).toArray();
}
}
}
现在您的错误少了很多,并且可以看到类型污染使流结果变得更糟,而不会影响for循环结果。对于像100x100这样的矩阵,差异非常显著。我正在添加一个包含并行开关的实现示例。我很好奇你们怎么看
/**
* Returns a {@link UnaryOperator} that transposes the matrix.
*
* Example {@code transpose(true).apply(m);}
*
* @param parallel
* Whether to perform the transpose concurrently.
*/
public static UnaryOperator<ArrayMatrix> transpose(boolean parallel) {
return (m) -> {
double[][] data = m.getData();
IntStream stream = range(0, m.getColumnDimension());
stream = parallel ? stream.parallel() : stream;
double[][] transpose =
stream.mapToObj(
column -> range(0, data.length).mapToDouble(row -> data[row][column]).toArray())
.toArray(double[][]::new);
return new ArrayMatrix(transpose);
};
}
/**
*返回转置矩阵的{@link UnaryOperator}。
*
*示例{@code transpose(true).apply(m);}
*
*@param并行
*是否同时执行转置。
*/
公共静态一元运算符转置(布尔并行){
返回(m)->{
double[]data=m.getData();
@State(Scope.Benchmark)
public static class MatrixContainer {
@Param({"true", "false"})
private boolean pollute;
@Param({ "100", "1000", "3000" })
private int matrixSize;
private double[][] matrix;
@Setup(Level.Iteration)
public void setUp() {
ThreadLocalRandom random = ThreadLocalRandom.current();
matrix = random.doubles(matrixSize)
.mapToObj(i -> random.doubles(matrixSize).toArray())
.toArray(double[][]::new);
if(!pollute) return;
// do some seemingly harmless operations which will
// poison JIT compiler type profile with some other lambdas
for(int i=0; i<100; i++) {
range(0, 1000).map(x -> x+2).toArray();
range(0, 1000).map(x -> x+5).toArray();
range(0, 1000).mapToObj(x -> x*2).toArray();
range(0, 1000).mapToObj(x -> x*3).toArray();
}
}
}
Benchmark (matrixSize) (pollute) Mode Cnt Score Error Units
StreamTest.forLoopTranspose 100 true avgt 50 0,033 ± 0,001 ms/op
StreamTest.forLoopTranspose 100 false avgt 50 0,032 ± 0,001 ms/op
StreamTest.forLoopTranspose 1000 true avgt 50 17,094 ± 0,060 ms/op
StreamTest.forLoopTranspose 1000 false avgt 50 17,065 ± 0,080 ms/op
StreamTest.forLoopTranspose 3000 true avgt 50 260,173 ± 7,855 ms/op
StreamTest.forLoopTranspose 3000 false avgt 50 258,774 ± 7,557 ms/op
StreamTest.streamTranspose 100 true avgt 50 0,096 ± 0,001 ms/op
StreamTest.streamTranspose 100 false avgt 50 0,055 ± 0,012 ms/op
StreamTest.streamTranspose 1000 true avgt 50 21,497 ± 0,439 ms/op
StreamTest.streamTranspose 1000 false avgt 50 15,883 ± 0,265 ms/op
StreamTest.streamTranspose 3000 true avgt 50 272,806 ± 8,534 ms/op
StreamTest.streamTranspose 3000 false avgt 50 260,515 ± 9,159 ms/op
/**
* Returns a {@link UnaryOperator} that transposes the matrix.
*
* Example {@code transpose(true).apply(m);}
*
* @param parallel
* Whether to perform the transpose concurrently.
*/
public static UnaryOperator<ArrayMatrix> transpose(boolean parallel) {
return (m) -> {
double[][] data = m.getData();
IntStream stream = range(0, m.getColumnDimension());
stream = parallel ? stream.parallel() : stream;
double[][] transpose =
stream.mapToObj(
column -> range(0, data.length).mapToDouble(row -> data[row][column]).toArray())
.toArray(double[][]::new);
return new ArrayMatrix(transpose);
};
}