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);
    };
}