簡體   English   中英

多線程程序比單線程程序慢

[英]Multithreaded Program slower than singlethreaded

我創建了一個非常簡單的測試程序,用於計算一些標量 c 和矩陣 A 的 c*A。 您可以在此處在線運行它,也可以將以下代碼粘貼到您喜歡的文本編輯器中:

#include <iostream>
#include <time.h>
#include <chrono>
#include <thread>

void fill_rand_matrix(double* mat, int n){
    
    for (int i=0;i<n;i++){
        mat[i]=static_cast <double> (rand()) / static_cast <double> (RAND_MAX)*20-10;
    }
}

void test(size_t m, size_t n, double alpha, double* X) {

        for (int j = 0; j < m; ++j) {
            for (int i = 0; i < n; ++i) {
                X[i+ j*n] *= alpha;
            }
        }       
}   

int main()
{
    int m=10000;
    int n=10000;
    double res_scaling=0.5;
    
    double* res=new double[m*n];
    fill_rand_matrix(res,n*m);
    
    auto begin1 = std::chrono::steady_clock::now();
    std::thread t1(test,0.5*m,n,res_scaling,res);
    std::thread t2(test,0.5*m,n,res_scaling,(double*)(res+(m/2)*n));
    t1.join();
    t2.join();
    auto end1= std::chrono::steady_clock::now();
    std::cout << "Time taken multithreaded = " << std::chrono::duration_cast<std::chrono::milliseconds>(end1 - begin1).count() << "[ms]" << std::endl;

    auto begin2 = std::chrono::steady_clock::now();
    test(m,n,res_scaling,res);
    auto end2= std::chrono::steady_clock::now();
    std::cout << "Time taken singlethreaded = " << std::chrono::duration_cast<std::chrono::milliseconds>(end2 - begin2).count() << "[ms]" << std::endl;

    return 0;
}

當我多次運行這段代碼時,多線程版本要么比單線程版本快一點,要么甚至更慢。 如果我添加超過 2 個線程,甚至會發生這種情況。 多線程似乎沒有任何好處,盡管問題幾乎可以隨着內核數量的增加而完美地擴展。 此外,我將矩陣大小設置得越高,運行時間的波動就越劇烈,有時波動幅度高達 20 倍。

你知道這里發生了什么嗎?

我的知識不夠,無法給出明確的答案,但由於目前沒有答案,我會盡力而為:


簡短的回答:

對大小為L的數組(其中L大於最大緩存的大小)的多次順序迭代將無法利用任何緩存來訪問數組的新緩存行(因為緩存使用變體LRU )。 使用快速計算快速迭代大小為L的數組意味着訪問(主)內存是瓶頸,並且會占用所有正在運行的進程/線程的共享內存總線 添加更多也受主內存訪問約束的線程只會導致它們之間的競爭。

(如果非常聰明,你的緩存可能會在每個新的數組數據進入 L2 緩存之前丟棄它,意識到在它被推出之前你無法使用它。這不會影響這個過程,但會為其他人留下更多的緩存空間。)


更多信息:

對我來說,使用g++ -std=c++17 -O3 -pthread -S -fverbose-asm

...使用與該行相關聯的兩次movups指令給出匯編輸出:

X[i+ j*n] *= alpha; // line 17

movupsx86 並行化 (SIMD) 指令 像這樣的 SIMD 指令通常將 4 個double放在一個巨大的寄存器上以非常快速地進行計算(大約 10 個時鍾周期,但如果我錯,請糾正我)。 將此乘以 4 以在緩存行上完成工作:~40 個周期。 如果您不使用 x86,那么您可能使用的是具有類似並行化指令的 CPU。

主內存訪問很慢(大約需要 100-200 個時鍾周期才能從主內存中獲取緩存行 [緩存行 = 64 字節塊 ~= 16 雙倍])。

您的 CPU 將嘗試通過預取數據來幫助您,但是,因為您總是以比數據總線可以提供的速度更快的速度從主內存請求數據,所以預取可能只會幫助減少您的等待時間~100 到 ~60,如果你幸運的話。 無論哪種方式,等待主內存訪問仍然是瓶頸。

請注意,這也適用於另一個方向,您可以使用更新的數組值填充緩存,但是在 8MB 左右之后,您需要不斷地將該數據發送回主內存。 所以我們上面的估計確實是樂觀的。


挑剔:

test功能有個小bug:

j < mi < n是有符號/無符號比較。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM