在java中使用多线程添加整数2D数组元素比顺序添加慢

时间:2013-12-20 04:20:34

标签: java arrays multithreading performance

所以,我在java中练习多线程并试图按顺序和使用4个线程添加随机生成的2D整数数组的元素。我测量了我的代码的性能,由于某种原因,顺序部分比多线程快得多。以下是顺序添加的代码:

public class ArraySum2DNonMT {

private int[][] arrayToSum;
private int totalSum;

public ArraySum2DNonMT(int[][] arr){
    this.arrayToSum = arr;
    this.setTotalSum(0);
}

public void runSequential(){
    for(int i = 0; i < arrayToSum[0].length; i++){
        for(int j = 0; j < arrayToSum.length; j++){
            setTotalSum(getTotalSum() + arrayToSum[j][i]);
        }
    }
}

public int getTotalSum() {
    return totalSum;
}

public void setTotalSum(int totalSum) {
    this.totalSum = totalSum;
}

}

以下是多线程版本的代码:

package multiThreaded;

/**
* 
* @author Sahil Gupta
* 
* This class takes in a 2D integer array and adds it's contents. This
* addition will be concurrent between several threads which will divide
* the work of the array based on the threadID assigned to thread by the
* programmer. Assume that the passed in 2D array to the constructor is a 
* matrix with each array in the main array having same length.
*/

public class ArraySum2D implements Runnable{

private int[][] arrayToSum;
private int threadID;
private int totalSum;

public ArraySum2D(int[][] arr, int threadID){
    this.arrayToSum = arr;
    this.threadID = threadID;
    this.setTotalSum(0);
}

@Override
public void run() {
    int arrayCol = arrayToSum[0].length;
    int arrayRow = arrayToSum.length;
    int colStart = (int)((threadID%2) * (arrayCol/2));
    int rowStart = (int)((int)(threadID/2) * (arrayRow/2));
    int colEnd = colStart + (int)(arrayCol/2);
    int rowEnd = rowStart + (int)(arrayRow/2);

    for(int i = colStart; i < colEnd; i++){
        for(int j = rowStart; j < rowEnd; j++){
            setTotalSum(getTotalSum() + arrayToSum[j][i]);
        }
    }
}

public int getTotalSum() {
    return totalSum;
}

public void setTotalSum(int totalSum) {
    this.totalSum = totalSum;
}

}

这是主要的:

package controller;

import java.util.Random;

import multiThreaded.ArraySum2D;
import sequentialNonMT.ArraySum2DNonMT;

public class ControllerMain {

private final static int cols = 20;
private final static int rows = 10;
private static volatile int[][] arrayToAdd = new int[rows][cols];
private static Random rand = new Random();
private static ArraySum2D a0, a1, a2, a3;

public static void main(String[] args) throws InterruptedException{

    for(int j = 0; j < rows; j++){
        for(int i = 0; i < cols; i++){
            arrayToAdd[j][i] = rand.nextInt(100);
        }
    }

    ArraySum2DNonMT a = new ArraySum2DNonMT(arrayToAdd);

    long startTimeSequential = System.nanoTime();
    a.runSequential();
    long estimatedTimeSequential = System.nanoTime() - startTimeSequential;

    System.out.println("The total sum calculated by sequential program is: " + a.getTotalSum());
    System.out.println("The total time taken by sequential program is: " + estimatedTimeSequential);

    a0 = new ArraySum2D(arrayToAdd, 0);
    a1 = new ArraySum2D(arrayToAdd, 1);
    a2 = new ArraySum2D(arrayToAdd, 2);
    a3 = new ArraySum2D(arrayToAdd, 3);
    Thread t0 = new Thread(a0);
    Thread t1 = new Thread(a1);
    Thread t2 = new Thread(a2);
    Thread t3 = new Thread(a3);

    long startTimeMultiThreaded = System.nanoTime();
    t0.start();
    t1.start();
    t2.start();
    t3.start();

    t0.join();
    t1.join();
    t2.join();
    t3.join();
    int Sum = addThreadSum();
    long estimatedTimeMultiThreaded = System.nanoTime() - startTimeMultiThreaded;

    System.out.println("The total sum calculated by multi threaded program is: " + Sum);
    System.out.println("The total time taken by multi threaded program is: " + estimatedTimeMultiThreaded);
}

private static int addThreadSum(){
    return a0.getTotalSum() + a1.getTotalSum() + a2.getTotalSum() + a3.getTotalSum();
}

}

我目前获得的输出显示运行时间的显着差异(此处以纳秒为单位测量)。这就是我得到的:

The total sum calculated by sequential program is: 10109 
The total time taken by sequential program is: 46000
The total sum calculated by multi threaded program is: 10109
The total time taken by multi threaded program is: 641000

顺序代码快了大约13倍。能帮我指出一下我可能做得不好吗?我有一个双核i7 haswell,macbook air。我不确定为什么需要更长的时间,但是我想到的一些想法可能会导致这种情况:虚假共享,太多并行/线程(双核为4),缓存一致性协议可能不利于我,我缺少/不知道的一些其他基本多线程的东西。

请帮助我确定具体原因以及我可以使多线程运行比顺序运行更快的方法。非常感谢你帮助我!

编辑:有关处理器及其缓存的更多信息:     处理器名称:Intel Core i7     处理器速度:1.7 GHz     处理器数量:1     核心总数:2     L2缓存(每个核心):256 KB     L3缓存:4 MB

根据英特尔的数据表,我认为最多可以有4个线程。

P.S。这是我提出问题的第一篇帖子,但我一直在使用这个网站来澄清一段时间的疑虑。请原谅我犯的任何错误。

2 个答案:

答案 0 :(得分:1)

建立线程时有sizable amount of overhead。也就是说,如果您的示例数据集太小,那么启动和拆除线程所花费的时间将大于代码的实际运行时间性能。

让我们主观地看一下。您有一个只包含200个元素的数组。您的方法的运行时间为O(nm),其中 n 是行大小, m 是列大小。

坦率地说,我希望以这种方式快速翻译200个元素的唯一一种机器将是我的旧Pentium III机器。即便如此, 也不会远离它。

我有一个相对强劲的i7-4770K,它可以做4个核心,每个核心有两个线程。如果我使用较低的数字运行你的程序,我会得到相同的结果。

但是......如果我将界限放大一点怎么办?设 n = 2 ** m *,让 n = 9000。

不要注意总和。整数溢出完全破坏了我们从中获得的任何价值。

The total sum calculated by sequential program is: -570429863
The total time taken by sequential program is: 3369190200
The total sum calculated by multi threaded program is: -570429863
The total time taken by multi threaded program is: 934624554

线程版本在27%的时间内运行,或者快了大约3.6倍。或者以外行人的话说,3.36秒对934毫秒。这是巨大

线程并没有改变算法的性能 - 它在O(nm)上仍然非常低效 - 但它确实改变了运行时常量,因此它不是很接近,而是接近1/4的时间。我能够从中获得优势的唯一原因是由于我推送的数据大小。否则,线程就不值得。

答案 1 :(得分:0)

我完全赞同Makoto,至于为什么你看到多线程程序变慢了:线程创建开销使这么小的数组的计算时间相形见绌,正如你可以看到的那样数组大小。

虽然这并没有直接回答你的问题,但我认为你可能会觉得这很有趣,因为有一个微不足道的变化可以使两个版本更快。

考虑您的原始代码:

 for(int i = 0; i < arrayToSum[0].length; i++){
        for(int j = 0; j < arrayToSum.length; j++){
            setTotalSum(getTotalSum() + arrayToSum[j][i]);
        }
 }

在java中,实际上没有2D数组这样的东西;相反,2D数组实际上是一个数组引用数组。 (好参考:http://www.willamette.edu/~gorr/classes/cs231/lectures/chapter9/arrays2d.htm)在原始的for循环中,内部(j)循环遍历遍历所有数组,一次获取一个元素。 (即添加所有数组的第一个元素,然后添加第二个,等等。)大型数组上的这种行为几乎可以保证您的内存缓存没有得到任何帮助,因为您的代码非常差locality of reference

如果你交换迭代顺序,如下:

 for(int j = 0; j < arrayToSum.length; j++){ // <-- this used to be the inner loop
     for(int i = 0; i < arrayToSum[0].length; i++){
         setTotalSum(getTotalSum() + arrayToSum[j][i]);
     }
 }

现在你的内循环一次顺序遍历一个数组,你有很好的引用位置,这对缓存非常友好。

在我的机器上,第二个版本的运行速度几乎是第一个版本的 20倍。 (并且,如果您感兴趣,在我的机器上,多线程版本的运行速度与单线程版本一样快 2.8x ,因此当您将这两个更改组合在一起时,缓存友好,多线程数组求和运算的运行速度比原始,单线程,缓存恶意:)版本快<60>。