As part of one of my CS classes, I have to write a matrix class in Java, with some methods implemented in Java as well as C++ via the Java Native Interface and measure the difference in execution time.
Writing and debugging both versions was simple enough and after about 3 hours spent mostly googling how to get the interface to choose, I wound up with this following code:
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);
}
However for some reason, the Java implementation is as fast or faster for most input sizes, which is definitely not the expected result after talking to some classmates.
Here is some sample output data for different matrix sizes, taken from my Debian virtual box:
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
As you can see the time it takes for the native version to run is quite consistently longer, however the ratio of the two seems erratic and doesn't appear to follow a trend, however it's relatively stable when I re run the same sizes multiply times.
To make this even more weird, on my Macbook it follows an entirely different curve: It starts similarly, being near 2x slower for small sizes, at medium dimensions (around 100-200 lines/columns) it finishes in 20-30% of the time, then at big sizes it's neck-and-neck again.
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
The third call here is about what I was expecting from talking to my classmates, but the program will need to handle larger data as per the assignment. If anyone could explain what the heck is going on here, that would be great?