Multiplication De Matrice De Flux Java 8 10 Fois Plus Lente Que Pour La Boucle?


J'ai créé un module qui effectue une multiplication matricielle à l'aide de flux. Il peut être trouvé ici: https://github.com/firefly-math/firefly-math-linear-real/

J'ai essayé d'écrire un benchmark afin de comparer l'implémentation de la boucle stream à l'implémentation de la boucle for correspondante dans Apache Commons Math.

Le module de référence est ici: https://github.com/firefly-math/firefly-math-benchmark

Et le benchmark réel ici: https://github.com/firefly-math/firefly-math-benchmark/blob/master/src/main/java/com/fireflysemantics/benchmark/MultiplyBenchmark.java

Lorsque j'exécute le benchmark sur des matrices de taille 100X100 et 1000X1000, il s'avère que Apache Commons Math (utilise une boucle for) est 10 fois plus rapide (à peu près) que l'implémentation de flux correspondante.

# 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

Ai-je fait quelque chose de mal dans le benchmark (espérons-le :) )?

J'ajoute les méthodes testées de telle sorte que tout le monde puisse voir ce qui est comparé. Il s'agit de l'Apache Commons Math Array2DRowRealMatrix.multiplier() méthode:

/**
 * Returns the result of postmultiplying {@code this} by {@code m}.
 *
 * @param m matrix to postmultiply by
 * @return {@code this * m}
 * @throws DimensionMismatchException if
 * {@code columnDimension(this) != rowDimension(m)}
 */
public Array2DRowRealMatrix multiply(final Array2DRowRealMatrix m)
    throws DimensionMismatchException {
    MatrixUtils.checkMultiplicationCompatible(this, m);

    final int nRows = this.getRowDimension();
    final int nCols = m.getColumnDimension();
    final int nSum = this.getColumnDimension();

    final double[][] outData = new double[nRows][nCols];
    // Will hold a column of "m".
    final double[] mCol = new double[nSum];
    final double[][] mData = m.data;

    // Multiply.
    for (int col = 0; col < nCols; col++) {
        // Copy all elements of column "col" of "m" so that
        // will be in contiguous memory.
        for (int mRow = 0; mRow < nSum; mRow++) {
            mCol[mRow] = mData[mRow][col];
        }

        for (int row = 0; row < nRows; row++) {
            final double[] dataRow = data[row];
            double sum = 0;
            for (int i = 0; i < nSum; i++) {
                sum += dataRow[i] * mCol[i];
            }
            outData[row][col] = sum;
        }
    }

    return new Array2DRowRealMatrix(outData, false);
}

Et c'est l'implémentation de flux correspondante:

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

TIA, Ole

Author: Ole, 2016-01-27

1 answers

Jetez un oeil à DoublePipeline.toArray:

public final double[] toArray() {
  return Nodes.flattenDouble((Node.OfDouble) evaluateToArrayNode(Double[]::new))
                    .asPrimitiveArray();
}

Il semble qu'un tableau en boîte soit créé en premier, puis converti en tableau primitif.

 1
Author: a better oliver, 2016-01-27 15:37:29