为什么我的方法的JNI实现比纯Java运行得慢?

时间:2017-10-02 18:25:26

标签: java c++ java-native-interface

作为我的一个CS类的一部分,我必须用Java编写一个矩阵类,其中一些方法通过Java Native Interface用Java和C ++实现,并测量执行时间的差异。

编写和调试这两个版本非常简单,大约花了3个小时后,他们大部分都在谷歌搜索如何选择界面,我接下来的代码如下:

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

然而由于某些原因,对于大多数输入大小,Java实现速度或速度都快,这绝对不是与某些同学交谈后的预期结果。

以下是一些不同矩阵大小的示例输出数据,取自我的Debian虚拟框:

axim@hackbox:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 5 12 8
time taken in Java: 11452ns
time taken in C++:  20990ns
results equal:      true
axim@hackbox:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 20 48 32
time taken in Java: 5439887ns
time taken in C++:  5492423ns
results equal:      true
axim@hackbox:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 80 192 128
time taken in Java: 19726130ns
time taken in C++:  25375681ns
results equal:      true
axim@hackbox:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 320 768 512
time taken in Java: 194357345ns
time taken in C++:  384648461ns
results equal:      true
axim@hackbox:~/Desktop/prcpp/jni$ java -Djava.library.path=. Matrix 1280 3072 2048
time taken in Java: 58514495266ns
time taken in C++:  116695035710ns
results equal:      true

正如您所看到的,本机版本运行所需的时间非常长,但两者的比例似乎不稳定且似乎不符合趋势,但是当我运行相同时它相对稳定大小乘以倍数。

为了使这更奇怪,在我的Macbook上它遵循完全不同的曲线:它开始类似,小尺寸接近2倍,中等尺寸(大约100-200行/列)它在20-30完成百分之一的时间,然后是大尺寸,它再次颈部和颈部。

axim@ax1m-MBP:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 5 12 8
time taken in Java:     32454ns
time taken in C++:      43379ns
results equal:          true
axim@ax1m-MBP:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 20 48 32
time taken in Java:     1278592ns
time taken in C++:      103246ns
results equal:          true
axim@ax1m-MBP:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 80 192 128
time taken in Java:     12594845ns
time taken in C++:      2604591ns
results equal:          true
axim@ax1m-MBP:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 320 768 512
time taken in Java:     1272993352ns
time taken in C++:      1217730765ns
results equal:          true
axim@ax1m-MBP:~/Desktop/CodeStuff/prcpp/a1/matrix$ java Matrix 1280 3072 2048
time taken in Java:     110882859155ns
time taken in C++:      102803692425ns
results equal:          true

这里的第三个电话是关于我与同学交谈的期望,但程序需要根据作业处理更大的数据。如果有人能解释这里到底发生了什么,那会很棒吗?

1 个答案:

答案 0 :(得分:0)

在编译代码时尝试使用-O3;)

首先,您不必为输入的数组提交更改。如果您将JNI_ABORT用于不需要传递回Java的数组,您将在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

您可以在此处详细了解JNI_ABORT:http://jnicookbook.owsiak.org/recipe-No-013/

如果我应该编写这段代码,我会把你的,v,w传递给C ++,我会在那里创建数组,我会创建输出数组并将其传递给Java。减少数据的复制粘贴方式;)