pthread를 이용한 병렬처리 시 속도 저하 문제

Hoewon Kim@Google의 이미지

pthread를 이용한 병렬처리에 대하여 공부하고 있습니다.

아래 소스코드와 같이 서로 다른 4개의 메모리에 대한 동일한 처리를 4개의 thread를 생성하여 처리하고, 4번에 걸쳐 순차적으로 처리한 결과를 비교해 보았습니다.

#include <stdio.h>
#include <iostream>
#include <pthread.h>
#include <time.h>
#include <cpuid.h>
 
#define MULTITHREAD_NUM 4
#define SIZE_OF_DATA 4000*3000
#define SIZE_OF_ONETHR SIZE_OF_DATA/MULTITHREAD_NUM
 
#define CPUID(INFO, LEAF, SUBLEAF) __cpuid_count(LEAF, SUBLEAF, INFO[0], INFO[1], INFO[2], INFO[3])
 
#define GETCPU(CPU) {                              \
    uint32_t CPUInfo[4];                           \
    CPUID(CPUInfo, 1, 0);                          \
    /* CPUInfo[1] is EBX, bits 24-31 are APIC ID */ \
    if ( (CPUInfo[3] & (1 << 9)) == 0) {           \
        CPU = -1;  /* no APIC on chip */             \
    }                                              \
    else {                                         \
        CPU = (unsigned)CPUInfo[1] >> 24;                    \
    }                                              \
        if (CPU < 0) CPU = 0;                          \
    }
 
 
struct threadwork_arg
{
    unsigned char* src;
    unsigned char* dst;
    unsigned char* gain;
    int i_start;
    int i_end;
    int thread_id;
};
 
void* test_func(void* arg)
{
    clock_t begin_t, end_t;
 
    threadwork_arg* param = (threadwork_arg*)arg;
 
    int i;
    unsigned char* src = param->src;
    unsigned char* dst = param->dst;
    unsigned char* gain = param->gain;
    int i_start = param->i_start;
    int i_end = param->i_end;
    int loop_size = i_end - i_start;
    int cnt = 0;
    int cpu_id;
    unsigned int temp_data_dst;
    unsigned int temp_data_src;
    unsigned int temp_data_gain;
 
    GETCPU(cpu_id);
 
    begin_t = clock();
    printf("thread%d start %lu\n", param->thread_id, begin_t);
    for(i=0; i<loop_size; i++)
    {
        temp_data_src = (unsigned int)*src;
        temp_data_gain = (unsigned int)*gain;
        temp_data_dst = temp_data_src*temp_data_gain>>8;
        *dst = (unsigned char)(temp_data_dst&0xFF);
        dst++;src++;gain++;cnt++;
    }
    end_t = clock();
    printf("thread%d end %lu\n", param->thread_id, end_t);
    printf("thread%d %d loop processing time %4.5fms cpu[%d]\n", param->thread_id, cnt, (double)(end_t - begin_t)*1000/CLOCKS_PER_SEC, cpu_id);
 
    return arg;
}
 
int main(int argc, const char * argv[])
{
    clock_t begin_t, end_t;
    threadwork_arg thread_param[MULTITHREAD_NUM];
    pthread_t threadwork_hello[MULTITHREAD_NUM];
 
    int idx = 0;
    int thr_id[MULTITHREAD_NUM];
    int status[MULTITHREAD_NUM];
 
    unsigned char* src_data[MULTITHREAD_NUM];
    unsigned char* dst_data[MULTITHREAD_NUM];
    unsigned char* gain_data[MULTITHREAD_NUM];
 
    int cpu_id;
    GETCPU(cpu_id);
    printf("%s cpu[%d]\n", __func__, cpu_id);
 
    for(int i=0; i<MULTITHREAD_NUM; i++)
    {
        src_data[i] = new unsigned char[SIZE_OF_DATA];
        dst_data[i] = new unsigned char[SIZE_OF_DATA];
        gain_data[i] = new unsigned char[SIZE_OF_DATA];
 
        memset(src_data[i], 64, SIZE_OF_DATA);
        memset(dst_data[i], 0, SIZE_OF_DATA);
        memset(gain_data[i], 128, SIZE_OF_DATA);
    }
 
    begin_t = clock();
    printf("whole loop start %lu\n", begin_t);
    for(idx=0; idx<MULTITHREAD_NUM; idx++)
    {
        thread_param[idx].src = src_data[idx];
        thread_param[idx].dst = dst_data[idx];
        thread_param[idx].gain = gain_data[idx];
        thread_param[idx].i_start = 0;
        thread_param[idx].i_end = thread_param[idx].i_start + SIZE_OF_ONETHR;
        thread_param[idx].thread_id = idx;
    }
 
    for(idx=0; idx<MULTITHREAD_NUM; idx++)
    {
        GETCPU(cpu_id);
        printf("%s create thread%d cpu[%d]\n", __func__, idx, cpu_id);
        thr_id[idx] = pthread_create(&threadwork_hello[idx], NULL, test_func, (void*)&thread_param[idx]);
        if (thr_id[idx] < 0)
        {
            perror("thread create error : ");
            exit(0);
        }
    }
 
    for(idx=0; idx<MULTITHREAD_NUM; idx++)
    {
        pthread_join(threadwork_hello[idx], (void **)&status);
    }
    end_t = clock();
    printf("whole loop end %lu\n", end_t);
    printf("whole loop time %4.5fms\n", (double)(end_t - begin_t)*1000/CLOCKS_PER_SEC);
 
 
 
    begin_t = clock();
    printf("no thread loop start %lu\n", begin_t);
    for(idx=0; idx<MULTITHREAD_NUM; idx++)
    {
        thread_param[idx].thread_id = idx - MULTITHREAD_NUM;
        test_func((void*)&thread_param[idx]);
    }
    end_t = clock();
    printf("no thread loop end %lu\n", end_t);
    printf("no thread loop time %4.5fms\n", (double)(end_t - begin_t)*1000/CLOCKS_PER_SEC);
 
 
    for(int i=0; i<MULTITHREAD_NUM; i++)
    {
        delete[] src_data[i];
        delete[] dst_data[i];
        delete[] gain_data[i];
    }
 
    return 0;
}

실행 결과

main cpu[4]
whole loop start 97671
main create thread0 cpu[4]
main create thread1 cpu[4]
thread0 start 97736
main create thread2 cpu[4]
thread1 start 97761
main create thread3 cpu[4]
thread2 start 97927
thread3 start 98037
thread2 end 104471
thread2 3000000 loop processing time 6.54400ms cpu[5]
thread0 end 107325
thread0 3000000 loop processing time 9.58900ms cpu[0]
thread3 end 108334
thread3 3000000 loop processing time 10.29700ms cpu[7]
thread1 end 108485
thread1 3000000 loop processing time 10.72400ms cpu[3]
whole loop end 108530
whole loop time 10.85900ms
no thread loop start 108538
thread-4 start 108539
thread-4 end 109466
thread-4 3000000 loop processing time 0.92700ms cpu[2]
thread-3 start 109479
thread-3 end 110503
thread-3 3000000 loop processing time 1.02400ms cpu[6]
thread-2 start 110517
thread-2 end 111441
thread-2 3000000 loop processing time 0.92400ms cpu[6]
thread-1 start 111451
thread-1 end 112356
thread-1 3000000 loop processing time 0.90500ms cpu[6]
no thread loop end 112370
no thread loop time 3.83200ms

단순히 생각하기에는 각각의 thread가 서로 다른 core에서 실행되었으니 적어도 각 thread에 할당된 test_func의 동작 시간 만큼은 thread를 생성하지 않고 처리했을 때의 test_func의 동작 시간과 동일한 수준이어야 할 것 같습니다.
그러나 실행 결과를 보면 각 thread에서 동작한 test_func의 동작 시간이 상대적으로 매우 느립니다. 그 이유가 무엇인지 도저히 감이 오지 않네요...

라스코니의 이미지

우선 pthread_create()을 시작하는데 상당한 시간이 소요됩니다. 시간 측정 시작을 pthread_join() 이전으로 옮긴다면 보다 공평한(?) 시간 비교가 가능할 것 같고요.

쓰레드 내부에서 printf()와 같은 출력 함수는 정밀한 시간 측정을 방해할 수 있으니 참고하세요.

Hoewon Kim@Google의 이미지

printf나 create의 오버헤드는 감안하고 있습니다. 제가 신경 쓰이는 부분은 test_func() 가 동작하는 시간이 너무 심하게 다르다는 점입니다. printf의 오버헤드를 감안하더라도 10ms와 1ms의 차이는 너무한 것 같아서요...

라스코니의 이미지

위에 하신 것을 보면 test_func() 만 차이점을 보도록 되어 있지 않습니다. 중간에 create(), join() 함수가 끼어들고 있습니다. 이것은 영향을 제거하고 난 후 비로서 test_func()의 실행 시간 차이를 비교할 수 있을 것입니다.

Hoewon Kim@Google의 이미지

test_func 내부에도 시간 측정하고 있습니다.

라스코니의 이미지

음. 제가 잘못 보았었네요.
그럼 한번 순서를 바꾸어서 해보시죠? no thread test 다음에 whole loop 테스트를요.
캐쉬 효과도 좀 있을 것 같은 상황입니다.

Hoewon Kim@Google의 이미지

감사합니다. 시도해 봐야겠군요!

furmuwon의 이미지

소스를 보고 시간이 저렇게 큰 갭으로 나오는 이유가 도무지 짐작이 안되어
무려!!! 2시간 이상을 투자해서 살펴 보았습니다(ㅜㅜ 업무시간인데..)

갭이 너무너무 큰 이유는 시간 측정 미스로 보입니다.

전 이때까지 함수 시간 측정 할 때 gettimeofday 를 사용했는데..
clock(); 은 처음 봅니다?

검색해보니 함수 측정할 때 clock 을 많이 사용한다고 했는데
오픈소스를 그렇게 많이 아는 것은 아니지만
clock 으로 시간 측정 하는 놈은 보질 못했네요

https://github.com/tomxuetoy/Linux_calculate_time/blob/master/calculate_time.c

그외로
struct itimerval e1, e2;
double elapsed;

setitimer(ITIMER_REAL, &(struct itimerval){{1000,0},{1000,0}}, NULL);

getitimer(ITIMER_REAL, &e1);
getitimer(ITIMER_REAL, &e2);
elapsed = (e1.it_value.tv_sec - e2.it_value.tv_sec) + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0);

이렇게도 사용합니다.
https://github.com/Seagate/SMR_FS-EXT4/blob/master/hdparm/hdparm.c

(hdparm 프로그램 시간 측정 방법 입니다! 명성이 있으니 믿을만 하죠?)

그래도 시간 차이가 나는건 좀더 스터디를 해봐야 할 것 같네요..

main cpu[3]
whole loop start 64508
main create thread0 cpu[10]
main create thread1 cpu[10]
thread0 start 66223
main create thread2 cpu[10]
thread1 start 68315
main create thread3 cpu[10]
thread2 start 71631
thread3 start 79648
thread0 end 128511
thread0 3000000 loop processing time 62.28800ms cpu[3]
thread0 3000000 running time : (gettimeofday)17860us, (getitimer)12.90500ms
thread1 end 130594
thread1 3000000 loop processing time 62.27900ms cpu[5]
thread1 3000000 running time : (gettimeofday)16860us, (getitimer)13.52900ms
thread2 end 139556
thread2 3000000 loop processing time 67.92500ms cpu[0]
thread2 3000000 running time : (gettimeofday)18109us, (getitimer)16.44400ms
thread3 end 143490
thread3 3000000 loop processing time 63.84200ms cpu[13]
thread3 3000000 running time : (gettimeofday)19399us, (getitimer)19.40100ms
whole loop end 143540
whole loop time 79.03200ms
no thread loop start 143558
thread-4 start 143563
thread-4 end 161954
thread-4 3000000 loop processing time 18.39100ms cpu[10]
thread-4 3000000 running time : (gettimeofday)18401us, (getitimer)18.40100ms
thread-3 start 161966
thread-3 end 175299
thread-3 3000000 loop processing time 13.33300ms cpu[10]
thread-3 3000000 running time : (gettimeofday)13335us, (getitimer)13.33500ms
thread-2 start 175312
thread-2 end 188756
thread-2 3000000 loop processing time 13.44400ms cpu[10]
thread-2 3000000 running time : (gettimeofday)13446us, (getitimer)13.44600ms
thread-1 start 188768
thread-1 end 201802
thread-1 3000000 loop processing time 13.03400ms cpu[10]
thread-1 3000000 running time : (gettimeofday)13060us, (getitimer)13.06000ms
no thread loop end 201812
no thread loop time 58.25400ms

Hoewon Kim@Google의 이미지

아...그렇군요... 소중한 시간 투자해 주셔서 감사합니다ㅠㅠ

furmuwon의 이미지

쓰레드에서 호출하는 함수가 시간이 많이 걸리는 이유는
쓰레드 개수를 확 늘리니 체감이 오는데요

#define MULTITHREAD_NUM 30
#define SIZE_OF_DATA 1024*1024*10
#define SIZE_OF_ONETHR SIZE_OF_DATA

이렇게 한번 수행해 보세요~ 병렬이 얼마나 빠른지 바로 알 수 있네요..

다음과 같이 추측 됩니다.
쓰레드로 돌리면 수행하는 시점에 동시에 30*30MB access 가 일어나고
싱글로 돌리면 수행하는 시점에 동시에 30MB access 가 시차적으로 30번 일어 납니다.

메모리와 CPU 간에 병렬이 아니니 30*30MB 가 직렬로 전송되므로
시간이 걸리는 쓰레드안 테스트 함수는 메모리 제어권을 기다림 인한 것 아닐까 싶네요

하지만 총 전송량 대비 수행 시간을 보면
쓰레드로 돌린것은 2초에 끝났고
싱글로 돌린것은 15초만에 끝났습니다.
(초 시간은 단순하게 어림 잡아 예로 든것입니다.. ㅎㅎ)

말그대로 create 이후 join 다음 시간을 보시면 될 것 같네요...

(모두 추측임... ㅎㅎ)

Hoewon Kim@Google의 이미지

아... 그럴 수도 있을 것 같네요. 앞으로 multi-threading 설계를 하는데에 참고가 될 것 같습니다. 감사합니다!

shint의 이미지

예전에 윈도우에서 해봤었는데.
스레드가 반복하는게 느리더라구요. ㅇ_ㅇ;;

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

Hoewon Kim@Google의 이미지

저도 프로세스를 써야 하나... 근데 프로세스를 쓴다고 나아질까... 하는 생각을 했었는데, 말씀 들어보니 시도해 봐야겠네요. 김사합니다!

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.