2011-11-29 74 views
0

我已經在C中實現了一個工作隊列模式(在一個python擴展中),我對性能感到失望。python c擴展:多線程和隨機數

我有一個粒子列表(「元素」)的模擬,我基準執行時間步所需的所有計算所需的時間,並將其與所涉及的粒子數一起記錄下來。我正在四核心超線程i7上運行代碼,所以我期望性能會上升(花費的時間),線程數可達8個,但相反,最快的實現沒有工作線程(功能簡單執行,而不是添加到隊列中),並且每個工作線程的代碼變得越來越慢(對於每個新線程來說,執行時間超過了無線執行的時間!)我已經快速瀏覽了我的處理器使用情況應用程序,並且看起來python永遠不會超過130%的CPU使用率,無論有多少線程正在運行。該機器具有足夠的空間,整體系統使用率約爲200%。

現在我的隊列執行(如下圖所示)的一部分在從隊列中隨機是選擇一個項目,因爲每個工作項目的執行需要兩個元素鎖和相似的元素將接近對方在隊列中。因此,我希望線程選擇隨機索引並攻擊隊列的不同位以最小化互斥衝突。

現在,我讀過,我與rand()初步嘗試將一直緩慢,因爲我的隨機數字是不是線程安全的(沒有這句話有道理嗎?不知道......)

我試着在random()drand48_r(儘管,不幸的是,後者似乎在OS X上不可用)的實現都與統計無關。

也許別人可以告訴我什麼可能是問題的原因?代碼(工作者函數)在下面,如果你認爲任何queue_add函數或構造函數也可能對你有用,那麼請大聲呼喊。

void* worker_thread_function(void* untyped_queue) { 

    queue_t* queue = (queue_t*)untyped_queue; 
    int success = 0; 
    int rand_id; 
    long int temp; 
    work_item_t* work_to_do = NULL; 
    int work_items_completed = 0; 

    while (1) { 
    if (pthread_mutex_lock(queue->mutex)) { 

     // error case, try again: 
     continue; 
    } 

    while (!success) { 

     if (queue->queue->count == 0) { 

     pthread_mutex_unlock(queue->mutex); 
     break; 
     } 

     // choose a random item from the work queue, in order to avoid clashing element mutexes. 
     rand_id = random() % queue->queue->count; 

     if (!pthread_mutex_trylock(((work_item_t*)queue->queue->items[rand_id])->mutex)) { 

     // obtain mutex locks on both elements for the work item. 
     work_to_do = (work_item_t*)queue->queue->items[rand_id]; 

     if (!pthread_mutex_trylock(((element_t*)work_to_do->element_1)->mutex)){ 
      if (!pthread_mutex_trylock(((element_t*)work_to_do->element_2)->mutex)) { 

      success = 1; 
      } else { 

      // only locked element_1 and work item: 
      pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex); 
      pthread_mutex_unlock(work_to_do->mutex); 
      work_to_do = NULL; 
      } 
     } else { 

      // couldn't lock element_1, didn't even try 2: 
      pthread_mutex_unlock(work_to_do->mutex); 
      work_to_do = NULL; 
     } 
     } 
    } 

    if (work_to_do == NULL) { 
     if (queue->queue->count == 0 && queue->exit_flag) { 

     break; 
     } else { 

     continue; 
     } 
    } 

    queue_remove_work_item(queue, rand_id, NULL, 1); 
    pthread_mutex_unlock(work_to_do->mutex); 

    pthread_mutex_unlock(queue->mutex); 

    // At this point, we have mutex locks for the two elements in question, and a 
    // work item no longer visible to any other threads. we have also unlocked the main 
    // shared queue, and are free to perform the work on the elements. 
    execute_function(
     work_to_do->interaction_function, 
     (element_t*)work_to_do->element_1, 
     (element_t*)work_to_do->element_2, 
     (simulation_parameters_t*)work_to_do->params 
    ); 

    // now finished, we should unlock both the elements: 
    pthread_mutex_unlock(((element_t*)work_to_do->element_1)->mutex); 
    pthread_mutex_unlock(((element_t*)work_to_do->element_2)->mutex); 

    // and release the work_item RAM: 
    work_item_destroy((void*)work_to_do); 
    work_to_do = NULL; 

    work_items_completed++; 
    success = 0; 
    } 
    return NULL; 
} 

回答

0

它似乎不像random()是你的問題,因爲無論線程數是多少,它都是相同的代碼。由於性能隨線程數量下降,可能會因鎖定開銷而死亡。你真的需要多個線程嗎?工作功能需要多久,平均隊列深度是多少?隨機選擇項目似乎是一個壞主意。當然,如果隊列計數爲< = 2,則不需要進行蘭德計算。另外,不要隨意選擇隊列索引,而應該爲每個工作線程使用不同的隊列並以循環方式插入。或者,至少像記住上一個線索聲明的最後一個索引那樣簡單,並且不會選擇那個索引。

+0

粒子的數量是從N = 100到100,000的任何值,並且您需要至少執行該數目計算,可能更像10N。對於大數量,時間步長可能需要一秒鐘,並且當你需要運行其中的50,000時間才能達到平衡... :)但是,謝謝,循環賽可能只是爲了最大限度地減少鎖定而不需要大量的隨機計算。 – tehwalrus

+0

你可以做一些分析,但是一般來說像rand()這樣的僞隨機數生成器(對於它來說足夠好)實際上並不是非常計算密集型的。它們可以通過幾次乘法實現,並增加結果溢出或使用反饋移位寄存器。密碼隨機生成器可以是重量級的,但你絕對不需要這樣做。 – TJD

+0

的確如此,但循環法也減少了對其他鎖定行爲的需求,並且在任何時候都可以使除一個線程外的所有線程都不受妨礙地進入工作隊列。通過實現它的一半方法,但是當多於一個線程時會讓段錯誤變得令人沮喪:/所以很快就會知道! :) – tehwalrus

0

Python線程不是真正的線程。所有python線程都運行在同一個OS級別的線程中,並且由於GIL(全局解釋器鎖)而一次執行一次。如果員工在上下文中相對較長時間,用流程重寫代碼可能會有訣竅。

Wikipedia's page on GIL

---- ----編輯

權,這是在C。但GIL仍然很重要。 Info on threads in c extensions

+0

等待,Python正在搞亂我使用像pthread這樣的系統C庫的方式?我認爲C擴展和純C程序一樣快,在用C編寫的位中,不是? – tehwalrus

+1

我不完全確定它是否設法直接阻止你的pthreads,但行爲聽起來像我期望使用常規python線程。在文檔中我沒有看到任何關於它的具體內容,只要您不在線索中觸摸python對象即可。 http://docs.python.org/c-api/init.html#non-python-created-threads –

+0

有趣。在這種情況下,我沒有這樣做,但是我的代碼可能有一部分。謝謝! – tehwalrus

0

要知道這是否是您程序的瓶頸,您必須進行基準測試和檢查,但它可能是可行的。

random()和有隱藏狀態變量的朋友可能是並行編程的嚴重瓶頸。 如果它們是線程安全的,這通常是通過簡單訪問來實現的,所以一切都會變慢。

POSIX系統上線程安全隨機生成器的便攜式選擇是erand48。與drand48相反,它接收狀態變量作爲參數。你只需要在每個線程的堆棧上保留一個狀態變量(這是一個unsigned short[3]),並且用這個變量調用erand48

還請記住,這些是隨機生成器。如果你在不同的線程之間使用相同的狀態變量,你的隨機數不是獨立的。