简体   繁体   中英

Multi-threaded Matrix Multiplication in Java. Average times are off. Am I using executors correctly?

I'm trying to do mutli-threaded matrix multiplication in which I compare the execution times for a different number of threads starting from 1 to 100, incremented by 10 each iteration.

Basically I create two 100x100 matrices with random numbers ranging from -10.0 to 10.0 in their cells and then multiply them together. I will do that 25 times using a different amount of threads (again incremented by 10 each time: so the first iteration will use 1 thread, second iterations will use 10 threads, third will use 20 threads etc...) and find the average completion time and store that time in a file.

The problem I'm having is that I'm not exactly sure if I'm using the Executors correctly. For example, to me this snippet of code (i've also provided the entire program code below this snippet) is saying that I've created 10 threads and in each thread I will use the .execute method to run my LoopTaskA which happens to be the multiplication of the matrices. So what I am trying to do is have one multiplication that is split across these 10 threads. Is that what I'm doing here? Or am I multiplying 10 times across 10 threads (ie one multiplication per thread)?

The reason i'm asking this is because when I read the entire program, for every increase in thread count I get an increase in the average completion time. Shouldn't the completion time be reduced if I increase the number of threads since i'm splitting the workload?

According to this other question I found on this same website: maybe not? But i'm still unsure about what i'm doing wrong.

for(int i = 0; i < 25; i++)
{
    ExecutorService execService = Executors.newFixedThreadPool(10);
    startTime = System.nanoTime();          
    for(int j = 0; j < 10; j++)
    {
        execService.execute(new LoopTaskA(m1,m2));
    }     

import java.util.*;
import java.io.*;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MatrixMultiplication { 
    static double[][] m1 = new double[100][100];
    static double[][] m2 = new double[100][100];

public static void main(String[] args){

    long startTime;
    long endTime;
    long completionTime;
    ArrayList<Long> myTimes = new ArrayList<Long>();
    long addingNumber = 0;
    long averageTime;
    String filepath = "exe_time.csv";

    createMatrix();

    /*This for loop will create 1 thread and then use the execute method from execService
    to multiply the two 100x100 matrices together. The completionTime is how long it takes
    for the whole process to finish. We want to run this thread 25 times and then take the average
    of those completion times*/

    for(int i = 0; i < 25; i++)
    {

    ExecutorService execService = Executors.newFixedThreadPool(1);

    startTime = System.nanoTime(); 

    execService.execute(new LoopTaskA(m1,m2));

    execService.shutdown();

    endTime = System.nanoTime();

    completionTime = (endTime - startTime);

    myTimes.add(completionTime);

    System.out.println("The completion time for one iteration is: " + completionTime);

    }

    /*Takes the completion times that were stored in an arraylist and finds the average*/

    for(int i = 0; i < 25; i++)
    {
        addingNumber = addingNumber + myTimes.remove(0);
    }

    averageTime = (addingNumber / 25);
    System.out.println("The average run time in nanoseconds for 1 thread that ran 25 times is: " + averageTime);
    saveRecord(averageTime, filepath);

    /*We call createMatrix again here so we start with a fresh new matrix*/

    createMatrix();

    /*We are doing the same thing as before but now we have 10 threads and not 1*/

    for(int i = 0; i < 25; i++)
    {

    ExecutorService execService = Executors.newFixedThreadPool(10);

    startTime = System.nanoTime();

        for(int j = 0; j < 10; j++)
        {
            execService.execute(new LoopTaskA(m1,m2));
        } 

    execService.shutdown();

    endTime = System.nanoTime();

    completionTime = (endTime - startTime);

    myTimes.add(completionTime);

    System.out.println("The completion time for one iteration is: " + completionTime);

    }

    for(int i = 0; i < 25; i++)
    {
        addingNumber = addingNumber + myTimes.remove(0);
    }

    averageTime = (addingNumber / 25);
    System.out.println("The average run time in nanoseconds for 10 threads that ran 25 times is: " + averageTime);
    saveRecord(averageTime, filepath);

    createMatrix();

    /*We are doing the same thing as before but now we have 20 threads and not 10*/

    for(int i = 0; i < 25; i++)
    {

    ExecutorService execService = Executors.newFixedThreadPool(20);

    startTime = System.nanoTime();

        for(int j = 0; j < 20; j++)
        {
            execService.execute(new LoopTaskA(m1,m2));
        }

    execService.shutdown();

    endTime = System.nanoTime();

    completionTime = (endTime - startTime);

    myTimes.add(completionTime);

    System.out.println("The completion time for one iteration is: " + completionTime);

    }

    for(int i = 0; i < 25; i++)
    {
        addingNumber = addingNumber + myTimes.remove(0);
    }

    averageTime = (addingNumber / 25);
    System.out.println("The average run time in nanoseconds for 20 threads that ran 25 times is: " + averageTime);
    saveRecord(averageTime, filepath);  

 }

/*Creates the matrix input by taking a random number from the range of
    -10 to 10 and then truncates the number to two decimal places*/

public static double matrixInput(){
    double max = 10.0;
    double min = -10.0;

    Random ran = new Random();
    double random = min + (max - min) * ran.nextDouble();
    double truncatedRan = Math.floor(random*100)/100;
    return truncatedRan;

}

/*Places that random number generated in the matrixInput method into a cell of the matrix.
The goal is to create 2 random 100x100 matrices. The first 100x100 matrix is m1. The second is m2.*/

 public static void createMatrix(){

    for (int row = 0; row < m1.length; row++)
    {
        for (int col = 0; col < m1[0].length; col++)
        {
            m1[row][col] = matrixInput();
        }
    }

    for (int row = 0; row < m2.length; row++)
    {
        for (int col = 0; col < m2[0].length; col++)
        {
            m2[row][col] = matrixInput();
        }
    }

}

/*Method that creates a .csv (comma seperated vector) file which stores 
the average time*/

public static void saveRecord(long averageTime, String filepath)
{
    try
    {
        FileWriter fw = new FileWriter(filepath,true);
        BufferedWriter bw = new BufferedWriter(fw);
        PrintWriter pw = new PrintWriter(bw);

        pw.println(averageTime + ",");
        pw.flush();
        pw.close();

        System.out.println("File has been saved.");
    }   
    catch(Exception E)
    {
        System.out.println("File has NOT been saved.");
    }       
  } 
 }

 import java.util.*;
 public class LoopTaskA implements Runnable{

 double[][] m1;
 double[][] m2;

 @Override
 public void run(){     
    double sum = 0;     
    /*This is to calculate the resulting matrix.We need to know the number or rows of m1 
    and the number of columns in m2 (both of which will be 100 since we want a 100x100 matrix)*/

    double r[][] = new double [100][100];

    /*This multiplies the two 100x100 matrices together. You can think of i here as the row number (which is 100).
    The range of j will depend upon the number of columns in the resultant matrix (range of j = 100)
    The k value will depend upon the number of columns in the first matrix or the number of rows in 
    the second matrix, both of these 100*/
    for(int i = 0; i < 100; i++)
    {

        for(int j = 0; j < 100; j++)
        {
            for(int k = 0; k < 100; k++)
            {
                sum = sum + m1[i][k] * m2[k][j];
            }
            r[i][j] = Math.floor(sum*100)/100;
            sum = 0; //reset to 0 so you can do the calculation for the next value.
        }

    }

    /* for(int i = 0; i < 100; i++)
    {
        for(int j = 0; j < 100; j++)
        {
            System.out.print(r[i][j] + " ");
        }

            System.out.println();
    } */        
 }


 public LoopTaskA(double[][] m1, double[][] m2){
    this.m1 = m1;
    this.m2 = m2;
 }  
}

I found only one problem in your code, you should call awaitTermination to block current thread after shutdown . shutdown does not wait for previously submitted tasks to complete execution.


Shouldn't the completion time be reduced if I increase the number of threads since i'm splitting the workload?

No, the available hard resources(for example, number of processors) are bounded. Multithreading does not always bring higher performance, you can check this question.

Also, a ThreadPoolExecutor you created by Executors.newFixedThreadPool() is used to address specific problems:

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks , due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks.


So, technically, you are using ExecutorSevice the right way. But it does not gurantee that you can get higher performance when you increase the number of threads. And

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM