我希望将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;
};
}
思想?
答案 0 :(得分:4)
你可以:
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);
};
}
此代码不使用forEach
,但更喜欢mapToObj
和mapToDouble
将每行映射到其转置。我还将Function<double[][], double[][]>
更改为UnaryOperator<double[][]>
,因为返回类型相同。
然而,像assylias的回答一样,使用简单的for循环可能会更有效率。
示例代码:
public static void main(String[] args) {
double[][] m = { { 2, 3 }, { 1, 2 }, { -1, 1 } };
double[][] tm = transpose().apply(m);
System.out.println(Arrays.deepToString(tm)); // prints [[2.0, 1.0, -1.0], [3.0, 2.0, 1.0]]
}
我已经实现了一个JMH基准测试,比较上面的代码,for循环版本和上面的代码并行运行。使用大小为100,1000和3000的随机平方矩阵调用所有三种方法。结果是对于小矩阵,for
循环版本更快但是对于更大的矩阵,并行流解决方案在性能方面确实更好( Windows 10,JDK 1.8.0_66,i5-3230M @ 2.60 GHz ):
Benchmark (matrixSize) Mode Cnt Score Error Units
StreamTest.forLoopTranspose 100 avgt 30 0,026 ± 0,001 ms/op
StreamTest.forLoopTranspose 1000 avgt 30 14,653 ± 0,205 ms/op
StreamTest.forLoopTranspose 3000 avgt 30 222,212 ± 11,449 ms/op
StreamTest.parallelStreamTranspose 100 avgt 30 0,113 ± 0,007 ms/op
StreamTest.parallelStreamTranspose 1000 avgt 30 7,960 ± 0,207 ms/op
StreamTest.parallelStreamTranspose 3000 avgt 30 122,587 ± 7,100 ms/op
StreamTest.streamTranspose 100 avgt 30 0,040 ± 0,003 ms/op
StreamTest.streamTranspose 1000 avgt 30 14,059 ± 0,444 ms/op
StreamTest.streamTranspose 3000 avgt 30 216,741 ± 5,738 ms/op
基准代码:
@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);
}
}
答案 1 :(得分:3)
紧凑而高效:
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
transpose[c][r] = m[r][c];
请注意,如果您的Matrix
类包含double[][]
,则另一种选择是返回具有相同基础数组但交换列/行索引的视图。您可以节省复制,但由于缓存局部性较差,您可能会在迭代时获得更差的性能。
答案 2 :(得分:2)
如果您假设一个矩形输入(原始代码似乎依赖),您可以将其写为
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);
}
这可以并行运行,但我想你需要一个该死的大矩阵来获得它的好处。
答案 3 :(得分:2)
我的建议:对于简单的低级数学,你应该使用普通的旧for循环而不是Stream API。此外,您应该非常仔细地对这些代码进行基准测试。
至于@Tunaki基准。首先,您不应该限制1微秒的单次测量。 matrixSize = 100
的结果是完整垃圾:0,093 ± 0,054
和0,237 ± 0,134
:错误超过50%。请注意,在每次迭代之前和之后执行的时间测量不是神奇的,也需要时间。这样一个小间隔很容易被一些突然醒来的Windows服务所破坏,需要一些CPU周期来检查一些东西,然后又重新入睡。我通常将每个预热/测量时间设置为500毫秒,这个数字对我来说很舒服。
其次,在使用非常简单的有效负载测试Stream API时(例如将数字复制到原始数组),您应该始终使用类型配置文件污染进行测试,因为它确实很重要。在干净的基准测试中,JIT编译器可以将所有内容都内联到单个方法中,因为它知道,例如,在一些range
之后,您总是使用相同的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();
}
}
}
此外,我设置了5个分叉,因为Stream API JIT编译器的行为可能与运行不同。编译进入后台线程,并且由于种族可能会在编译点处有不同的分析信息,这可能会显着改变编译结果。因此,在fork中,结果将是相同的,但在它们之间它们可能完全不同。
我的结果是(Windows 7,Oracle JVM 8u45 64位,一些不是很新的i5-2410笔记本电脑):
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
现在你的错误少得多,并且看到类型污染会使流结果变差,同时不会影响循环结果。对于像100x100这样的矩阵,差异非常大。
答案 4 :(得分:0)
我添加了一个包含并行交换机的实现示例。我很好奇你们都在想什么。
/**
* 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);
};
}