2010-11-15 38 views
2

我試圖改變一些代碼,所以它可以使用多線程。當圍繞代碼放置一個Runnable時,我偶然發現了一個性能損失。線程內部的代碼比外部線程慢?

對於澄清:原代碼,讓我們把它

//doSomething 

了一個Runnable圍繞這樣的:

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

然後我提交了可運行的ChachedThreadPool的ExecutorService。這是我對多線程代碼的第一步,以查看代碼是否像原始代碼一樣以一個線程運行。

但是,情況並非如此。在大約2秒內執行// doSomething,Runnable執行約2.5秒。 我需要一提的是一些其他的代碼,說:// doSomethingElse,裏面一個Runnable 沒有性能損失相比原來// doSomethingElse

我的猜測是,// DoSomething的有一些操作在一個線程工作時不那麼快,但我不知道它可能還是什麼,在方面是區別//doSomethingElse

難道是使用final int []/float []數組使Runnable慢得多嗎? // doSomethingElse代碼也使用了一些決賽,但// doSomething使用更多。這是我能想到的唯一的事情。

不幸的是,代碼很長且不在上下文中,但我會在這裏發佈它。對於那些知道Mean Shift分段算法的人來說,這是代表每個像素正在計算均值漂移向量的代碼的一部分。通過每個像素中的for循環

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. 

有更多的代碼,但最後while循環是我第一次注意到,在性能上的差異。

任何人都可以想到爲什麼這段代碼在Runnable內運行得比原來慢嗎?

謝謝。

編輯:測量的時間是內部的代碼,所以不含啓動的線程的。

+1

如果你拿一些運行260ms的東西,把它包裝在一個Runnable中,並且只運行一次,那麼是的,由於線程的啓動開銷會花費更長的時間。一次運行一些東西並不是一個很好的線程使用。請解釋你爲什麼要運行這個多線程。 – 2010-11-15 14:34:51

+0

我提到其他代碼在Runnable內部和在外部一樣快。但是的確,總的運行時間比那個長。此代碼完成後,Runnable內部需要約3.5秒,而外部則需要2秒。我想以多線程的形式運行這個代碼,作爲應該實現的實時應用程序的一部分。巨大的for-loop(從0到L)可以分成幾部分。 – RemiX 2010-11-15 14:43:21

+0

根據您的編輯,並行代碼(和測試)的外觀如何? – gustafc 2010-11-15 14:55:55

回答

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); 
} 

你是否注意到結果有什麼不同?

+0

實際上,完成的任務相當大(需要2秒)。對不起,我沒有提到。那時開銷會是500毫秒,我不相信。另外,我在代碼中測量時間。 – RemiX 2010-11-15 14:57:00

0

當你創建一個新的線程,你總是有一個開銷。如果你有一小段代碼,你可能會遇到性能損失。 一旦你有更多的代碼(更大的任務),你讓你的並行化獲得的性能提升(線程的代碼不一定會跑得更快,但你同時做兩件事)。

只是一個細節:這到底有多大小可任務是如此並行還是值得的決定是在並行計算:)

+0

其實這個完整的任務是非常大的,因爲我已經編輯到我原來​​的文章。花費2秒完成(使用Runnable 2.5秒)。我想,500毫秒的開銷太大了。 – RemiX 2010-11-15 15:02:13

+0

我認爲你應該做的數學運算是:在一個線程中執行整個程序需要多長時間,並行執行需要多長時間。 這額外500ms可能是由其他線程搶佔「你的」,我猜想:) – JSBach 2010-11-16 08:19:20

1

我個人看不出有任何理由已知的話題。任何程序至少有一個線程。所有線程都是平等的。所有線程默認創建爲中等優先級(5)。因此,代碼應該在主應用程序線程和您打開的其他線程中顯示相同的性能。

您確定您正在測量「執行某些操作」的時間,而不是程序運行的總時間?我相信你正在測量操作時間以及創建和啓動線程所需的時間。

+0

是的,我很確定,因爲我已經編輯到我原來​​的職位;)謝謝。 – RemiX 2010-11-15 14:58:07

0

你還沒有解釋你如何測量所花的時間。顯然有線程啓動成本,但我推斷你正在使用一些機制,確保這些成本不扭曲你的圖片。

一般來說,在測量性能時,測量小塊工件時容易誤導。我希望能夠延長至少1000倍的運行時間,將整個過程放在一個循環或其他任何環境中。

在這裏,一個「無主題」和「線程」的情況下,實際上是你不必一個線程消失(如已經指出的那樣,你總是有一個線程)和2個線程之間的不同所以現在JVM必須在兩個線程之間進行調解。對於這種工作,我不明白爲什麼這應該有所作爲,但這是一個區別。

我想使用一個很好的分析工具來真正挖掘到這一點。

+0

我已將我的計時方法添加到原始帖子中。它位於大循環之外,總計大約2秒(使用Runnable的時間爲2.5秒)。關於一對兩個線程,如果第一個線程(啓動第二個線程的線程)等待第二個線程完成,這仍然會成爲問題嗎? – RemiX 2010-11-15 15:01:02

+0

無論如何,我懷疑它甚至是一個問題,我想我們可以很容易想象,如果兩個線程都很忙,那麼還有額外的工作,但我不明白你的情況如何適用。 – djna 2010-11-15 15:17:22