簡體   English   中英

為什么多線程(使用 pthread)似乎比多進程(使用 fork)慢?

[英]Why the multi-threading(using pthread) seems slower than multi-process(using fork)?

在這里,我嘗試使用 3 種方法添加 0 到 1e9 之間的所有數字:

  1. 普通順序執行(單線程)
  2. 創建多個進程以添加較小的部分(使用 fork)並在最后添加所有較小的部分,以及
  3. 創建多個線程以與第二種方法相同。

據我所知,線程創建速度很快,因此稱為輕量級進程。

但是在執行我的代碼時,我發現第二個方法(多進程)是最快的,其次是第一個方法(順序),然​​后是第三個(多線程)。 但我無法弄清楚為什么會這樣(可能是執行時間計算中的一些錯誤,或者我的系統中的某些內容有所不同,等等)。

這是我的代碼 C 代碼:

#include "stdlib.h"
#include "stdio.h"
#include "unistd.h"
#include "string.h"
#include "time.h"
#include "sys/wait.h"
#include "sys/types.h"
#include "sys/sysinfo.h"
#include "pthread.h"
#define min(a,b) (a < b ? a : b)

int n = 1e9 + 24; // 2, 4, 8 multiple 

double show(clock_t s, clock_t e, int n, char *label){
    double t = (double)(e - s)/(double)(CLOCKS_PER_SEC);
    printf("=== N %d\tT %.6lf\tlabel\t%s === \n", n, t, label);
    return t;
}

void init(){
    clock_t start, end;
    long long int sum = 0;
    start = clock();
    for(int i=0; i<n; i++) sum += i;
    end = clock();
    show(start, end, n, "Single thread");
    printf("Sum %lld\n", sum); 
}

long long eachPart(int a, int b){
    long long s = 0;
    for(int i=a; i<b; i++) s += i;
    return s;
}
// multiple process with fork
void splitter(int a, int b, int fd[2], int n_cores){ // a,b are useless (ignore)
    clock_t s, e;
    s = clock();
    int ncores = n_cores;
    // printf("cores %d\n", ncores);
    int each = (b - a)/ncores, cc = 0;
    pid_t ff; 
    for(int i=0; i<n; i+=each){
        if((ff = fork()) == 0 ){
            long long sum = eachPart(i, min(i + each, n) );
            // printf("%d->%d, %d - %d - %lld\n", i, i+each, cc, getpid(), sum);
            write(fd[1], &sum, sizeof(sum));
            exit(0);
        }
        else if(ff > 0) cc++;
        else printf("fork error\n");
    }
    int j = 0;
    while(j < cc){
        int res = wait(NULL);
        // printf("finished r: %d\n", res);
        j++;
    }
    long long ans = 0, temp;
    while(cc--){
        read(fd[0], &temp, sizeof(temp));
        // printf("c : %d, t : %lld\n", cc, temp);
        ans += temp;
    }
    e = clock();
    show(s, e, n, "Multiple processess used");
    printf("Sum %lld\tcores used %d\n", ans, ncores);
}


// multi threading used 
typedef struct SS{
    int s, e;
} SS;

int tfd[2];

void* subTask(void *p){
    SS *t = (SS*)p;
    long long *s = (long long*)malloc(sizeof(long long)); 
    *s = 0;
    for(int i=t->s; i<t->e; i++){
        (*s) = (*s) + i;
    }
    write(tfd[1], s, sizeof(long long));
    return NULL;
}

void threadSplitter(int a, int b, int n_thread){ // a,b are useless (ignore)
    clock_t sc, e;
    sc = clock();
    int nthread = n_thread;
    pthread_t thread[nthread];
    int each = n/nthread, cc = 0, s = 0;
    for(int i=0; i<nthread; i++){
        if(i == nthread - 1){
            SS *t = (SS*)malloc(sizeof(SS));
            t->s = s, t->e = n; // start and end point
            if((pthread_create(&thread[i], NULL, &subTask, t))) printf("Thread failed\n");
            s = n; // update start point
        }
        else {
            SS *t = (SS*)malloc(sizeof(SS));
            t->s = s, t->e = s + each; // start and end point
            if((pthread_create(&thread[i], NULL, &subTask, t))) printf("Thread failed\n");
            s += each; // update start point
        }
    }
    long long ans = 0, tmp;
    // for(int i=0; i<nthread; i++){
    //     void *dd;
    //     pthread_join(thread[i], &dd); 
    //     // printf("i : %d s : %lld\n", i, *((long long*)dd));
    //     ans += *((long long*)dd);
    // }
    int cnt = 0;
    while(cnt < nthread){
        read(tfd[0], &tmp, sizeof(tmp));
        ans += tmp;
        cnt += 1;
    }
    e = clock();
    show(sc, e, n, "Multi Threading");
    printf("Sum %lld\tThreads used %d\n", ans, nthread);
}

int main(int argc, char* argv[]){
    init();

    printf("argc : %d\n", argc);
    
    // ncore - processes
    int fds[2];
    pipe(fds);
    int cores = get_nprocs();
    splitter(0, n, fds, cores);
    for(int i=1; i<argc; i++){
        cores = atoi(argv[i]);
        splitter(0, n, fds, cores);
    }
    
    // nthread - calc
    pipe(tfd); 
    threadSplitter(0, n, 16);
    for(int i=1; i<argc; i++){
        int threads = atoi(argv[i]);
        threadSplitter(0, n, threads);
    }

    return 0;
}

輸出結果:

=== N 1000000024    T 2.115850  label   Single thread === 
Sum 500000023500000276
argc : 4
=== N 1000000024    T 0.000467  label   Multiple processess used === 
Sum 500000023500000276  cores used 8
=== N 1000000024    T 0.000167  label   Multiple processess used === 
Sum 500000023500000276  cores used 2
=== N 1000000024    T 0.000436  label   Multiple processess used === 
Sum 500000023500000276  cores used 4
=== N 1000000024    T 0.000755  label   Multiple processess used === 
Sum 500000023500000276  cores used 6
=== N 1000000024    T 2.677858  label   Multi Threading === 
Sum 500000023500000276  Threads used 16
=== N 1000000024    T 2.204447  label   Multi Threading === 
Sum 500000023500000276  Threads used 2
=== N 1000000024    T 2.235777  label   Multi Threading === 
Sum 500000023500000276  Threads used 4
=== N 1000000024    T 2.534276  label   Multi Threading === 
Sum 500000023500000276  Threads used 6

另外,我使用管道來傳輸子任務的結果。 在多線程中,我也嘗試使用連接線程並按順序合並結果,但最終結果在大約 2 秒的執行時間上相似。

輸出: 終端輸出

TL;DR:您以錯誤的方式測量時間。 使用clock_gettime(CLOCK_REALTIME, ...)而不是clock()


您正在使用clock()測量時間,如手冊頁所述:

[...] 返回程序使用的處理器時間的近似值。 [...] 返回的值是目前使用的 CPU 時間作為clock_t

clock()使用的系統時鍾測量 CPU 時間,即調用進程在使用 CPU 時所花費的時間。 進程使用的 CPU 時間是其所有線程使用的 CPU 時間的總和,但不是其子進程,因為它們是不同的進程。 另請參閱: UNIX 中的掛鍾時間、用戶 CPU 時間和系統 CPU 時間具體是什么?

因此,在您的 3 個場景中會發生以下情況:

  1. 沒有並行性,順序代碼。 運行該進程所花費的 CPU 時間幾乎是所有可以測量的,並且與實際花費的掛鍾時間非常相似。 請注意,單線程程序的 CPU 時間始終低於或等於其掛鍾時間。

  2. 多個子進程。 由於您正在創建子進程來代表主(父)進程完成實際工作,因此父進程將使用幾乎為零的 CPU 時間:它唯一需要做的就是一些系統調用來創建子進程,然后是一些系統調用等待他們退出。 它的大部分時間都花在等待孩子的睡眠上,而不是在 CPU 上運行。 子進程是在 CPU 上運行的進程,但您根本沒有測量它們的時間。 因此,您最終的時間很短(1 毫秒)。 你在這里基本上沒有測量任何東西。

  3. 多線程。 由於您正在創建 N 個線程來完成工作,並且僅在主線程中占用 CPU 時間,因此您的進程的 CPU 時間將占線程的 CPU 時間總和。 毫不奇怪,如果您進行完全相同的計算,每個線程花費的平均 CPU 時間為 T/NTHREADS,將它們相加將得到 T/NTHREADS * NTHREADS = T。實際上,您大致使用的是與第一個場景相同的 CPU 時間,只有一點點用於創建和管理線程的開銷。

所有這些都可以通過兩種方式解決:

  1. 在每個線程/進程中以正確的方式仔細考慮 CPU 時間,然后根據需要繼續對這些值求和或求平均值。
  2. 使用帶有CLOCK_REALTIME clock_gettime簡單地測量掛鍾時間(即真實的人類時間)而不是 CPU 時間。 有關更多信息,請參閱手冊頁

暫無
暫無

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

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