2013-07-01 84 views
3

我需要使用Opus解碼器儘可能快地解碼音頻數據。C++多線程解碼音頻數據

目前我的申請不夠快。解碼速度儘可能快,但我需要獲得更多的速度。

我需要解碼大約100段音頻。 T 這些部分不是連續的(它們彼此不相關)。

我正在考慮使用多線程,因此我不必等到100個解碼中的一個完成。在我的夢中,我可以同時開始一切。 我以前沒有使用過多線程。

因此,我想問問我的方法是否總體良好,或者是否存在某種思維錯誤。

謝謝。

+0

如果您希望操作可以在不依賴於對方的情況下完成,它們可能是並行運行的好候選對象。 –

+0

@MarkGarcia你能告訴我應該怎麼做?解碼器是否必須是單獨的exe文件,還是我可以在我的主應用程序中實現? – tmighty

+0

您可以檢查[基於任務的並行性](http://msdn.microsoft.com/zh-cn/library/dd492427.aspx)或[parallel :: for_each](http://msdn.microsoft.com/zh-cn/ -us/library/dd492418.aspx),[tbb](http://threadingbuildingblocks.org/)也可以使用 –

回答

2

這個答案很可能將來自社區需要一點點的改進,因爲它是一個很長一段時間,因爲我曾在這樣的環境中,但這裏是一個開始 -

因爲你是新的多線程在C++,從一個簡單的項目開始,創建一堆執行簡單任務的pthread。

以下是創建並行線程的快速和小例子:

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 

void* ThreadStart(void* arg); 

int main(int count, char** argv) { 
     pthread_t thread1, thread2; 

     int* threadArg1 = (int*)malloc(sizeof(int)); 
     int* threadArg2 = (int*)malloc(sizeof(int)); 

     *threadArg1 = 1; 
     *threadArg2 = 2; 

     pthread_create(&thread1, NULL, &ThreadStart, (void*)threadArg1); 
     pthread_create(&thread2, NULL, &ThreadStart, (void*)threadArg2); 

     pthread_join(thread1, NULL); 
     pthread_join(thread2, NULL); 
     free(threadArg1); 
     free(threadArg2); 
} 

void* ThreadStart(void* arg) { 

     int threadNum = *((int*)arg); 
     printf("hello world from thread %d\n", threadNum); 

     return NULL; 
} 

接下來,你將要使用多個OPUS解碼器。 Opus似乎是線程安全的,只要您爲每個線程創建單獨的OpusDecoder對象。

爲了養活工作到你的線程,你需要能夠在一個線程安全的方式訪問待審批工作單位的名單。您可以使用std::vectorstd::queue,但在添加到它時以及從中移除時,您必須在其周圍使用鎖,並且您需要使用計數信號量,以便線程可以阻止但保持活動狀態慢慢地將工作單元添加到隊列中(例如,從磁盤讀取的文件的緩衝區)。

以下是上述類似的一些示例代碼,演示如何使用共享隊列,以及如何使線程等待您填寫的隊列:

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <queue> 
#include <semaphore.h> 
#include <unistd.h> 

void* ThreadStart(void* arg); 

static std::queue<int> workunits; 
static pthread_mutex_t workunitLock; 
static sem_t workunitCount; 

int main(int count, char** argv) { 
    pthread_t thread1, thread2; 

    pthread_mutex_init(&workunitLock, NULL); 
    sem_init(&workunitCount, 0, 0); 

    pthread_create(&thread1, NULL, &ThreadStart, NULL); 
    pthread_create(&thread2, NULL, &ThreadStart, NULL); 

    // Make a bunch of workunits while the threads are running. 
    for (int i = 0; i < 200; i++){ 
     pthread_mutex_lock(&workunitLock); 

     workunits.push(i); 
     sem_post(&workunitCount); 

     pthread_mutex_unlock(&workunitLock); 

     // Pretend that it takes some effort to create work units; 
     // this shows that the threads really do block patiently 
     // while we generate workunits. 
     usleep(5000); 
    } 

    // Sometime in the next while, the threads will be blocked on 
    // sem_wait because they're waiting for more workunits. None 
    // of them are quitting because they never saw an empty queue. 
    // Pump the semaphore once for each thread so they can wake 
    // up, see the empty queue, and return. 

    sem_post(&workunitCount); 
    sem_post(&workunitCount); 

    pthread_join(thread1, NULL); 
    pthread_join(thread2, NULL); 

    pthread_mutex_destroy(&workunitLock); 
    sem_destroy(&workunitCount); 

} 

void* ThreadStart(void* arg) { 

    int workUnit; 
    bool haveUnit = true; 

    while(haveUnit) { 
     sem_wait(&workunitCount); 

     pthread_mutex_lock(&workunitLock); 

     // Figure out if there's a unit, grab it under 
     // the lock, then release the lock as soon as we can. 
     // After we release the lock, then we can 'process' 
     // the unit without blocking everybody else. 
     haveUnit = !workunits.empty(); 

     if (haveUnit) { 
      workUnit = workunits.front(); 
      workunits.pop(); 
     } 
     pthread_mutex_unlock(&workunitLock); 

     // Now that we're not under the lock, we can spend 
     // as much time as we want processing the workunit. 
     if (haveUnit) { 
      printf("Got workunit %d\n", workUnit); 
     } 
    } 

    return NULL; 
} 
+0

恩,對不起所有的編輯:) – antiduh

0

在加入多線程之前,作爲加快速度的解決方案,研究在訂閱下超額訂閱&的概念。

如果音頻處理涉及長時間阻塞IO調用,那麼多線程是值得的。

+0

你似乎有一個很好的觀點,但請你不要這麼神祕嗎? – tmighty

+0

我不是故意要哭泣,讓我知道如果我們需要擴大任何一個黨的點? –

+0

你的意思是說,如果缺少速度是由於磁盤訪問(= IO),多線程不工作(因爲一個磁盤不能同時訪問多次),但如果我真的在談論解碼完成這個CPU可能值得嗎? – tmighty

0

雖然你的問題含糊不清並不能真正幫助...怎麼樣:

Create a list of audio files to convert. 

While there is a free processor, 
    launch the decoder application with the next file in the queue. 
Repeat until there is nothing else in the list 

如果在測試過程中,你會發現處理器並不總是100%忙碌,推出2每處理器進行解碼。

它可以很容易地用bash/tcl/python來完成。

3

你會打亂你的工作任務。讓我們假設你的過程實際上是CPU綁定的(你指出它是,但是......通常並不那麼簡單)。

現在,你解碼100節:

我想使用多線程,這樣我就不必等到100個解碼的一個完成。在我的夢中,我可以同時開始一切。

實際上,您應該使用一個接近機器核心數量的數字。假設一個現代桌面(例如2-8個核心),一次運行100個線程只會減慢速度;內核會浪費大量時間從一個線程切換到另一個線程,並且該進程也可能使用更高的峯值資源並爭奪類似的資源。

所以,只需創建一個任務池,將活動任務的數量限制爲核心數量。每個任務將(通常)表示對一個輸入文件(節)執行的解碼工作。這樣,解碼過程實際上並不是在多個線程之間共享數據(允許您避免鎖定和其他資源爭用)。

完成後,返回並微調任務池中的進程數(例如,在多臺機器上使用完全相同的輸入和秒錶)。最快速度可能低於或高於內核數量(很可能是因爲磁盤I/O)。它也有助於配置文件。

因此,我想問一下,如果我的方法總體良好,或者在某處存在思維錯誤。

是的,如果問題是CPU綁定,那一般是好的。這也假設您的解碼器/相關軟件能夠運行多個線程。

如果這些磁盤上的文件,你會發現的問題是,你可能會需要從多核心優化你怎麼讀(寫?)的文件。因此,允許它一次運行8個作業可以使您的問題變成磁盤綁定 - 而且同時使用8個讀寫器是使用硬盤的一種不好的方式,因此您可能會發現它不如您預期的那麼快。因此,您可能需要爲您的並行解碼實現優化I/O。在這方面,使用更大的緩衝區大小,但這是在內存中的成本。

+1

好的答案通常你應該擁有的線程數是#(機器上的#個內核)* 2 – Adrian

+1

'threads = n * 2'?我通常對'n <= 2'使用'threads = n',對於'2 10'使用'threads = n * 1.2'; – antiduh

+1

請參閱http://stackoverflow.com/a/13958877/1007845(如果您對CPU和任務進行了一些測試,最好處理8個線程,每個核心有40個線程) – Adrian

2

而不是讓你自己的線程和管理他們的,我建議你使用一個線程池,讓您的解碼任務到池中。池會將任務分配給它和系統可以處理的任意數量的線程。雖然有不同類型的線程池,所以您可以設置一些參數,例如強制它使用某些特定數量的線程,或者如果您應該允許該池繼續增加線程數。

有一點要記住的是,更多的線程並不意味着它們並行執行。我認爲,正確的說法是並行的,除非你有每個線程不同的CPU(這將使真正的並行)上運行保障

您的整個池可以停下來,如果阻塞IO。

0

您可以在一般的使用線程,但鎖定有一些的問題。我將圍繞POSIX線程和鎖定來回答這個問題,但這是相當普遍的,您可以將這個想法移植到任何平臺上。但是,如果您的工作需要任何形式的鎖定,您可能會發現以下內容有用。此外,最好還是繼續使用現有的線程,因爲線程創建代價高昂(請參閱線程池)。

對於「實時」音頻,鎖定通常是一個糟糕的主意,因爲它會增加延遲,但這對於解碼/編碼的實時作業來說是完全可以的,即使對於實時的作業,您也可以獲得更好的性能並且不會丟幀通過使用一些線程知識。

對於音頻,信號量是一個糟糕的主意。至少我的系統(POSIX信號燈)在我嘗試時速度太慢,但如果您正在考慮跨線程鎖定(而不是鎖定在一個線程中並在同一線程中解鎖的類型),則需要它們。 POSIX互斥體只允許自鎖和自解鎖(你必須在同一個線程中執行這兩個操作),否則程序可能會工作,但它是未定義的行爲,應該避免。

大多數無鎖原子操作可能會讓您有足夠的自由來使用某些功能(如鎖定),但具有更好的性能。