Since I'm a very new student in Computer Sciences MSc, I'm learning a lot of new stuff about programming and especially about Java and C threads this week. Today, one my programming teacher asked us to make a Java exercice about threads to understand how they could be useful for your CPU load.
The thing is to generate a matrix and calcultate it average using some trigonometric math' operations. The benchmark uses the main class and a threading class ; both classes include the calculating method.
The concept is to calculate the whole matrix average using the un-threaded calculator to obtain a reference test and next to this test, calculate the same matrix using a defined number of threaded calculators.
Let's see with the Java code now:
/**
* Main.java
* @author 33xiT
*/
public class Main {
/**
* Calculate an average sum with a trigonometric calcul simulation to "load" CPU
* @param matrix
* @param matrix_size
* @return
*/
public static double calculateAverage(double[][] matrix, int matrix_size) {
double sum = 0.;
for (int i = 0; i < matrix_size; i++) {
for (int j = 0; j < matrix_size; j++) {
sum += matrix[i][j] * Math.atan2(Math.sqrt(matrix[i][j]), Math.pow(matrix[i][j], 2.9));
}
}
return sum;
}
/**
* Main class method
* Two optional parameters: int matrix_size int part_number
* @param args
*/
public static void main(String[] args) {
try {
int matrix_size = 4000;
int part_number = 4;
if (args.length == 2) {
matrix_size = new Integer(args[0]);
part_number = new Integer(args[1]);
}
long tt1, tr1, test_time, result_time = 0;
int start, gain = 0;
int part_size = matrix_size / part_number;
double test, result = 0.;
double matrix[][] = new double[matrix_size][];
ThreadedMatrixCalculator[] calculators = new ThreadedMatrixCalculator[part_number];
System.out.println("Matrix size: " + matrix_size + "\nPart number (thread number): " + part_number);
System.out.println("Matrix initialization...");
for (int i = 0; i < matrix_size; i++) {
matrix[i] = new double[matrix_size];
for (int j = 0; j < matrix_size; j++) {
matrix[i][j] = Math.random() * 10000;
}
}
System.out.println("Matrix initialized.");
System.out.println("Calculating matrix average w/o thread (test result)...");
tt1 = System.currentTimeMillis();
test = calculateAverage(matrix, matrix_size);
test_time = System.currentTimeMillis() - tt1;
System.out.println("Average w/o threading = " + test + " in " + test_time + " ms");
System.out.println("Calculating matrix average w/ thread...");
tr1 = System.currentTimeMillis();
for (int i = 0; i < part_number; i++) {
start = i * part_size;
if (i == part_number - 1 && (start + part_size) < matrix_size) {
part_size = matrix_size - start;
}
calculators[i] = new ThreadedMatrixCalculator(matrix, start, part_size);
calculators[i].start();
// System.out.println(calculators[i]);
}
for (int i = 0; i < part_number; i++) {
calculators[i].join();
result += calculators[i].getSum();
}
result_time = System.currentTimeMillis() - tr1;
gain = (int) (100 - ((result_time * 100) / test_time));
System.out.println("Average w/ threading = " + result + " in " + result_time + " ms");
System.out.println("Threaded calculting gain: ≈ +" + gain + "%");
} catch (OutOfMemoryError e) {
System.gc();
System.out.println("Error: " + e);
} catch (InterruptedException e) {
System.out.println("Error: " + e);
}
}
}
And now the threading class:
/**
* ThreadMatrixCalculator.java
* @author 33xiT
*/
public class ThreadedMatrixCalculator extends Thread {
protected double _sum = 0.;
protected double[][] _tab;
protected int _offset, _length;
/**
* Class constructor
* @param tab
* @param offset
* @param length
*/
public ThreadedMatrixCalculator(double[][] tab, int offset, int length) {
this._tab = tab;
this._offset = offset;
this._length = length;
}
/**
* Thread runner
*/
public void run() {
this.calculateAverage();
}
/**
* Sum getter
* @return
*/
public double getSum() {
return this._sum;
}
/**
* Calculate an average sum with a trigonometric calcul simulation to "load" CPU
*/
public void calculateAverage() {
for (int i = this._offset; i < (this._offset + this._length); i++) {
for (int j = 0; j < this._tab.length; j++) {
this._sum += this._tab[i][j] * Math.atan2(Math.sqrt(this._tab[i][j]), Math.pow(this._tab[i][j], 2.9));
}
}
}
/**
* Print thread informations about given offset and part_length
* @return
*/
public String toString() {
return new String(this + "@ [offset=" + this._offset + ",length=" + this._length + "]");
}
}
You can run it using an IDE or why not directly in your console:
% javac *.java
% java Main
Matrix size: 4000
Part number (thread number): 4
Matrix initialization...
Matrix initialized.
Calculating matrix average w/o thread (test result)...
Average w/o threading = 4771.371926919315 in 5336 ms
Calculating matrix average w/ thread...
Average w/ threading = 4771.371926918524 in 2878 ms
Threaded calculting gain: ≈ +47%
%
You can try with customized parameters, we want a matrix size of 2873 and we want 20 threads to calculate it average:
% java Main 2873 20
Matrix size: 2873
Part number (thread number): 20
Matrix initialization...
Matrix initialized.
Calculating matrix average w/o thread (test result)...
Average w/o threading = 2454.1397977533056 in 2755 ms
Calculating matrix average w/ thread...
Average w/ threading = 2454.1397977532015 in 1474 ms
Threaded calculting gain: ≈ +47%
%
As you could see, the gain is quite the same because it doesn't depend on the number of threads but of the number of cores your CPU has. I work on a MacBook Pro with a Core2 Duo CPU then the gain is about 50%... At the université, the gain is about 75% because we have Quad Core CPUs.
Well, I'm perhaps wrong with the analysis since I just discovered these stuff this week and not obviously perfectly understood. Thanks!