Dans le cadre de l'une de mes classes CS, je dois écrire une classe matricielle en Java, avec des méthodes implémentées en Java ainsi que C++ via l'interface native Java et mesurer la différence de temps d'exécution.Pourquoi l'implémentation JNI de ma méthode est-elle plus lente que Java?
L'écriture et le débogage des deux versions est assez simple et après environ 3 heures passées la plupart du temps googler comment obtenir l'interface de choisir, j'ai fini avec ce code suivant:
Matrix.java:
public class Matrix {
private double[] data;
private int width, height;
public Matrix(int h, int w) {
width = w;
height = h;
data = new double[w * h];
}
public static void main(String[] args) {
/* takes 3 parametres u, v and w, creates two matrices m1 and m2, dimensions u*v and v*w
* fills them with random doubles, multiplies m1 * m2 with both methods
* reports time elapsed and checks equality of result */
}
public Matrix multiply(Matrix mat) { return multiply(mat, false); }
public Matrix multiplyNative(Matrix mat) { return multiply(mat, true); }
public Matrix multiply(Matrix mat, boolean natively) {
int u, v, w;
u = this.height;
w = mat.width;
Matrix res = new Matrix(u, w);
if(this.width == mat.height) v = this.width;
else return res;
if(natively) multiplyC(this.data, mat.data, res.data, u, v, w);
else {
for(int i=0; i<u; i++) {
for(int j=0; j<w; j++) {
double elem = 0.0;
for(int k=0; k<v; k++) {
elem += this.data[i*v+k] * mat.data[k*w+j];
}
res.data[i*w+j] = elem;
}
}
}
return res;
}
public static native void multiplyC(double[] a, double[] b, double[] r, int i, int j, int k);
// SNIP: equals and random-prefill methods
static {
System.loadLibrary("Matrix");
}
}
Matrix.cpp:
#include "Matrix.h"
JNIEXPORT void JNICALL Java_Matrix_multiplyC(JNIEnv *env, jclass,
jdoubleArray a, jdoubleArray b, jdoubleArray res,
jint u, jint v, jint w) {
jdouble* mat1 = env->GetDoubleArrayElements(a, 0);
jdouble* mat2 = env->GetDoubleArrayElements(b, 0);
jdouble* mat_res = env->GetDoubleArrayElements(res, 0);
for(int i=0; i<u; i++) {
for(int j=0; j<w; j++) {
jdouble elem = 0.0;
for(int k=0; k<v; k++) {
elem += mat1[i*v+k] * mat2[k*w+j];
}
mat_res[i*w+j] = elem;
}
}
env->ReleaseDoubleArrayElements(a, mat1, 0);
env->ReleaseDoubleArrayElements(b, mat2, 0);
env->ReleaseDoubleArrayElements(res, mat_res, 0);
}
Howev Pour une raison quelconque, l'implémentation Java est aussi rapide ou plus rapide pour la plupart des tailles d'entrée, ce qui n'est certainement pas le résultat attendu après avoir parlé à certains camarades de classe.
Voici quelques données de sortie de l'échantillon pour différentes tailles de matrice, prises à partir de ma boîte virtuelle Debian:
[email protected]:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 5 12 8
time taken in Java: 11452ns
time taken in C++: 20990ns
results equal: true
[email protected]:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 20 48 32
time taken in Java: 5439887ns
time taken in C++: 5492423ns
results equal: true
[email protected]:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 80 192 128
time taken in Java: 19726130ns
time taken in C++: 25375681ns
results equal: true
[email protected]:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 320 768 512
time taken in Java: 194357345ns
time taken in C++: 384648461ns
results equal: true
[email protected]:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 1280 3072 2048
time taken in Java: 58514495266ns
time taken in C++: 116695035710ns
results equal: true
Comme vous pouvez le voir le temps qu'il faut pour la version native de fonctionner est tout à fait régulièrement plus, mais les le ratio des deux semble erratique et ne semble pas suivre une tendance, mais il est relativement stable quand je suis courir les mêmes tailles multiplier fois. Pour rendre cela encore plus bizarre, sur mon Macbook il suit une courbe entièrement différente: Il commence de même, étant près de 2x plus lent pour les petites tailles, à des dimensions moyennes (environ 100-200 lignes/colonnes), il se termine en 20- 30% du temps, puis à de grandes tailles c'est de nouveau le cou et le cou.
[email protected]:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 5 12 8
time taken in Java: 32454ns
time taken in C++: 43379ns
results equal: true
[email protected]:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 20 48 32
time taken in Java: 1278592ns
time taken in C++: 103246ns
results equal: true
[email protected]:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 80 192 128
time taken in Java: 12594845ns
time taken in C++: 2604591ns
results equal: true
[email protected]:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 320 768 512
time taken in Java: 1272993352ns
time taken in C++: 1217730765ns
results equal: true
[email protected]:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 1280 3072 2048
time taken in Java: 110882859155ns
time taken in C++: 102803692425ns
results equal: true
Le troisième appel est ici ce que je me attendais de parler à mes camarades de classe, mais le programme devra traiter les données plus importantes selon l'affectation. Si quelqu'un pouvait expliquer ce qui se passe ici, ce serait génial?
L'interfaçage du code C++ via jni a un coût qu'un bon compilateur java peut éliminer. – user0042
Cette question semble être un meilleur ajustement pour http://codereview.stackexchange.com/, même si cela aiderait à l'exprimer plus comme "cherchant des améliorations de performance pour l'appel de méthode natif (implémentation C++), pour dépasser celle du Java code " – Justin
IIRC, les tableaux sont copiés lorsqu'il est passé par le JNI, ce qui pourrait être la raison entière de votre performance hit – Justin