2017-10-02 1 views
1

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?

+0

L'interfaçage du code C++ via jni a un coût qu'un bon compilateur java peut éliminer. – user0042

+0

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

+0

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

Répondre

0

Essayez d'utiliser -O3 lors de la compilation de votre code;)

Tout d'abord, vous ne devez pas valider les modifications pour les tableaux qui sont entrés. Si vous utilisez JNI_ABORT pour les tableaux qui ne sont pas tenus d'être repassé à Java, vous obtiendrez plus rapidement des calculs en C++:

-O3 

java -Djava.library.path=. -cp . Matrix 5 12 8 
C++: 0 
java -Djava.library.path=. -cp . Matrix 20 48 32 
C++: 0 
java -Djava.library.path=. -cp . Matrix 80 192 128 
C++: 2 
java -Djava.library.path=. -cp . Matrix 320 768 512 
C++: 1254 
java -Djava.library.path=. -cp . Matrix 1280 3072 2048 
C++: 104179 

-O0 

java -Djava.library.path=. -cp . Matrix 5 12 8 
C++: 0 
java -Djava.library.path=. -cp . Matrix 20 48 32 
C++: 0 
java -Djava.library.path=. -cp . Matrix 80 192 128 
C++: 7 
java -Djava.library.path=. -cp . Matrix 320 768 512 
C++: 2400 
java -Djava.library.path=. -cp . Matrix 1280 3072 2048 
C++: 183814 

-O3 + JNI_ABORT 

java -Djava.library.path=. -cp . Matrix 5 12 8 
C++: 0 
java -Djava.library.path=. -cp . Matrix 20 48 32 
C++: 0 
java -Djava.library.path=. -cp . Matrix 80 192 128 
C++: 3 
java -Djava.library.path=. -cp . Matrix 320 768 512 
C++: 1121 
java -Djava.library.path=. -cp . Matrix 1280 3072 2048 
C++: 96696 

Java 

java -Djava.library.path=. -cp . Matrix 5 12 8 
Java: 0 
java -Djava.library.path=. -cp . Matrix 20 48 32 
Java: 1 
java -Djava.library.path=. -cp . Matrix 80 192 128 
Java: 13 
java -Djava.library.path=. -cp . Matrix 320 768 512 
Java: 1242 
java -Djava.library.path=. -cp . Matrix 1280 3072 2048 
Java: 101324 

Vous pouvez en savoir plus sur JNI_ABORT ici: http://jnicookbook.owsiak.org/recipe-No-013/

Si je devais pour écrire ce code, j'aurais passé u, v, w à C++, j'aurais créé des tableaux là-bas, et j'aurais créé un tableau de sortie et je l'aurais renvoyé à Java. Beaucoup moins de copier-coller de données;)