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!


Joris Berthelot