线程内部的代码比外部线程慢..?

时间:2010-11-15 14:21:01

标签: java multithreading performance final

我正在尝试更改一些代码,以便它可以与多线程一起使用。当我将Runnable放在一些代码时,我偶然发现了性能损失。

澄清:原始代码,我们称之为

//doSomething

像这样得到了一个Runnable:

Runnable r = new Runnable()
{
    public void run()
    {
        //doSomething
    }
}

然后我将runnable提交给ChachedThreadPool ExecutorService。这是我对这段代码进行多线程处理的第一步,看看代码是否运行得比原始代码一样快。

但事实并非如此。 // doSomething 在大约2秒内执行的地方,Runnable在大约2.5秒内执行。 我需要提一下,与原始 // doSomethingElse <相比,其他一些代码,例如 // doSomethingElse ,在Runnable 中没有性能损失 / em>的。

我的猜测是 // doSomething 有一些操作在线程中工作时不那么快,但我不知道它可能是什么或者是什么,在这方面是不同的使用 // doSomethingElse

是否可以使用 final int [] / float [] 数组使Runnable变得如此之慢? // doSomethingElse 代码也使用了一些决赛,但 // doSomething 使用了更多。这是我唯一能想到的。

不幸的是, // doSomething 代码很长且不在上下文中,但无论如何我都会在这里发布。对于那些知道Mean Shift分割算法的人来说,这是代码的一部分,其中为每个像素计算平均移位向量。 for-loop

for(int i=0; i<L; i++) 

贯穿每个像素。

timer.start(); // this is where I start the timer
// Initialize mode table used for basin of attraction
char[] modeTable = new char [L]; // (L is a class property and is about 100,000)
Arrays.fill(modeTable, (char)0);
int[] pointList = new int [L];

// Allcocate memory for yk (current vector)
double[] yk = new double [lN]; // (lN is a final int, defined earlier)

// Allocate memory for Mh (mean shift vector)
double[] Mh = new double [lN];

int idxs2 = 0; int idxd2 = 0;
for (int i = 0; i < L; i++) {
    // if a mode was already assigned to this data point
    // then skip this point, otherwise proceed to
    // find its mode by applying mean shift...
    if (modeTable[i] == 1) {
        continue;
    }

    // initialize point list...
    int pointCount = 0;

    // Assign window center (window centers are
    // initialized by createLattice to be the point
    // data[i])
    idxs2 = i*lN;
    for (int j=0; j<lN; j++)
    yk[j] = sdata[idxs2+j]; // (sdata is an earlier defined final float[] of about 100,000 items)

    // Calculate the mean shift vector using the lattice
    /*****************************************************/
    // Initialize mean shift vector
    for (int j = 0; j < lN; j++) {
        Mh[j] = 0;
    }
    double wsuml = 0;
    double weight;

    // find bucket of yk
    int cBucket1 = (int) yk[0] + 1;
    int cBucket2 = (int) yk[1] + 1;
    int cBucket3 = (int) (yk[2] - sMinsFinal) + 1;
    int cBucket = cBucket1 + nBuck1*(cBucket2 + nBuck2*cBucket3);
    for (int j=0; j<27; j++) {
        idxd2 = buckets[cBucket+bucNeigh[j]]; // (buckets is a final int[] of about 75,000 items)
        // list parse, crt point is cHeadList
        while (idxd2>=0) {
            idxs2 = lN*idxd2;
            // determine if inside search window
            double el = sdata[idxs2+0]-yk[0];
            double diff = el*el;
            el = sdata[idxs2+1]-yk[1];
            diff += el*el;
            //...
            idxd2 = slist[idxd2]; // (slist is a final int[] of about 100,000 items)
        }
    }
    //...
}
timer.end(); // this is where I stop the timer.

还有更多代码,但最后一次循环是我第一次注意到性能上的差异。

有没有人能想到这个代码在Runnable中运行速度比原来慢的原因?

感谢。

编辑:测量的时间是里面代码,所以不包括线程的启动。

4 个答案:

答案 0 :(得分:3)

所有代码始终在“线程内”运行。

您看到的减速很可能是由多线程添加的开销引起的。尝试并行化代码的不同部分 - 任务既不应该太大也不能太小。例如,您可能最好将每个外部循环作为单独的任务运行,而不是最内层的循环。

分割任务没有一种正确的方法,但这完全取决于数据的外观和目标机器的外观(2核,8核,512核?)。

编辑:如果反复运行测试会怎样?例如,如果你这样做:

Executor executor = ...;
for (int i = 0; i < 10; i++) {
    final int lap = i;
    Runnable r = new Runnable() {
        public void run() {
            long start = System.currentTimeMillis();
            //doSomething
            long duration = System.currentTimeMillis() - start;
            System.out.printf("Lap %d: %d ms%n", lap, duration);   
        }
    };
    executor.execute(r);
}

你注意到结果有什么不同吗?

答案 1 :(得分:1)

我个人认为没有任何理由。任何程序至少有一个线程。所有线程都是平等的。默认情况下,所有线程都创建为中等优先级(5)。因此,代码应该在主应用程序线程和您打开的其他线程中显示相同的性能。

您确定要测量“做某事”的时间而不是程序运行的总时间吗?我相信您正在测量操作时间以及创建和启动线程所需的时间。

答案 2 :(得分:0)

创建新线程时,总是会产生开销。如果您只有一小段代码,则可能会遇到性能损失。 一旦你有了更多的代码(更大的任务),你就可以通过并行化获得性能提升(线程上的代码不一定运行得更快,但你一次做两件事)。

只是一个细节:这个决定一个任务如此大小可以如此并行化它仍然是值得的并行计算中的一个已知主题:)

答案 3 :(得分:0)

您还没有准确解释如何衡量所用的时间。显然有线程启动成本,但我推断您正在使用某种机制来确保这些成本不会扭曲您的图片。

一般来说,在测量性能时,在测量小件工件时容易产生误导。我希望得到至少1000倍的运行时间,将整个事情放在一个循环或其他任何东西。

这里“无线程”和“线程”案例之间的不同之处实际上是你已经从一个线程中消失了(正如已经指出你总是有一个线程)和两个线程所以现在JVM必须在两个线程之间进行调解。对于这种工作,我不明白为什么这应该有所作为,但这是一个区别。

我希望使用一个好的分析工具来深入研究这个。