2017-02-22 7 views
1

win (Win7 x64, Corei7 3.4HGz) 및 Mac (10.12.3 Core i7 2.7 HGz)에서 openmp의 성능을 테스트하는 코드를 작성했습니다.LLVM/OpenMP에서 매우 느린 뮤텍스

  1. xcode에서 컴파일 된 기본값을 설정하는 콘솔 응용 프로그램을 만들었습니다. 내가 LLVM 3.7 및 OpenMP 5 (opm.h에서 KMP_VERSION_MAJOR = 5를 정의하고 KMP_VERSION_MINOR = 0을 정의하고 KMP_VERSION_BUILD = 20150701, libiopm5)를 MacOS 10.12.3 (CPU - Corei7 2700GHz)에서 찾는다.
  2. 나는 VS2010 Sp1을 사용한다. 추가 c/C++ -> 최적화 -> 최적화 = 속도 최대화 (O2), c/C++ -> 최적화 -> 선호 Soze 또는 속도 = 빠른 코드 (Ot)를 설정합니다.

응용 프로그램을 단일 스레드로 실행하면 시간 차이는 프로세서의 빈도 비율 (약)에 해당합니다. 그러나 4 개의 스레드를 실행하면 그 차이가 실체가됩니다. win program이 빠르면 70 번에서 mac 프로그램이 빨라집니다.

#include <cmath> 
#include <mutex> 
#include <cstdint> 
#include <cstdio> 
#include <iostream> 
#include <omp.h> 
#include <boost/chrono/chrono.hpp> 

static double ActionWithNumber(double number) 
{ 
    double sum = 0.0f; 
    for (std::uint32_t i = 0; i < 50; i++) 
    { 
     double coeff = sqrt(pow(std::abs(number), 0.1)); 
     double res = number*(1.0-coeff)*number*(1.0-coeff) * 3.0; 
     sum += sqrt(res); 
    } 
    return sum; 
} 

static double TestOpenMP(void) 
{ 
    const std::uint32_t len = 4000000; 
    double *a; 
    double *b; 
    double *c; 
    double sum = 0.0; 

    std::mutex _mutex; 
    a = new double[len]; 
    b = new double[len]; 
    c = new double[len]; 

    for (std::uint32_t i = 0; i < len; i++) 
    { 
     c[i] = 0.0; 
     a[i] = sin((double)i); 
     b[i] = cos((double)i); 
    } 
    boost::chrono::time_point<boost::chrono::system_clock> start, end; 
    start = boost::chrono::system_clock::now(); 
    double k = 2.0; 
    omp_set_num_threads(4); 
#pragma omp parallel for 
    for (int i = 0; i < len; i++) 
    { 
     c[i] = k*a[i] + b[i] + k; 
     if (c[i] > 0.0) 
     { 
      c[i] += ActionWithNumber(c[i]); 
     } 
     else 
     { 
      c[i] -= ActionWithNumber(c[i]); 
     } 
     std::lock_guard<std::mutex> scoped(_mutex); 
     sum += c[i]; 
    } 
    end = boost::chrono::system_clock::now(); 
    boost::chrono::duration<double> elapsed_time = end - start; 
    double sum2 = 0.0; 
    for (std::uint32_t i = 0; i < len; i++) 
    { 
     sum2 += c[i]; 
     c[i] /= sum2; 
    } 
    if (std::abs(sum - sum2) > 0.01) printf("Incorrect result.\n"); 
    delete[] a; 
    delete[] b; 
    delete[] c; 
    return elapsed_time.count(); 
} 

int main() 
{ 

    double sum = 0.0; 
    const std::uint32_t steps = 5; 
    for (std::uint32_t i = 0; i < steps; i++) 
    { 
     sum += TestOpenMP(); 
    } 
    sum /= (double)steps; 
    std::cout << "Elapsed time = " << sum; 
    return 0; 
} 

나는 "mac"과 "win"의 openmp 성능을 비교하기 위해 여기에 뮤텍스를 사용합니다. "Win"함수는 0.39 초의 시간을 반환합니다. "Mac"함수는 25 초, 즉 70 배 느린 시간을 반환합니다.

이 차이가 나는 원인은 무엇입니까?

먼저 내 게시물을 편집 해 주셔서 감사합니다 (텍스트를 쓰는 중역을 사용합니다). 실제 앱에서는 거대한 매트릭스 (20000х20000)의 값을 임의의 순서로 업데이트합니다. 각 스레드는 새 값을 결정하고이를 특정 셀에 씁니다. 대부분의 경우 다른 스레드가 다른 행에 쓰기 때문에 각 행에 대한 뮤텍스를 만듭니다. 하지만 분명히 2 개의 쓰레드가 한 줄에 쓰고 긴 자물쇠가있는 경우에. 현재 레코드의 순서는 FEM 요소에 의해 결정되므로 다른 스레드에서 행을 나눌 수 없습니다. 전체 매트릭스에 대한 쓰기를 차단하므로 중요한 섹션을 넣기 만하면됩니다.

나는 실제 응용 프로그램과 같은 코드를 작성했습니다.

static double ActionWithNumber(double number) 
{ 
    const unsigned int steps = 5000; 
    double sum = 0.0f; 
    for (u32 i = 0; i < steps; i++) 
    { 
     double coeff = sqrt(pow(abs(number), 0.1)); 
     double res = number*(1.0-coeff)*number*(1.0-coeff) * 3.0; 
     sum += sqrt(res); 
    } 
    sum /= (double)steps; 
    return sum; 
} 

static double RealAppTest(void) 
{ 
    const unsigned int elementsNum = 10000; 
    double* matrix; 
    unsigned int* elements; 
    boost::mutex* mutexes; 

    elements = new unsigned int[elementsNum*3]; 
    matrix = new double[elementsNum*elementsNum]; 
    mutexes = new boost::mutex[elementsNum]; 
    for (unsigned int i = 0; i < elementsNum; i++) 
     for (unsigned int j = 0; j < elementsNum; j++) 
      matrix[i*elementsNum + j] = (double)(rand() % 100); 
    for (unsigned int i = 0; i < elementsNum; i++) //build FEM element like Triangle 
    { 
     elements[3*i] = rand()%(elementsNum-1); 
     elements[3*i+1] = rand()%(elementsNum-1); 
     elements[3*i+2] = rand()%(elementsNum-1); 
    } 
    boost::chrono::time_point<boost::chrono::system_clock> start, end; 
    start = boost::chrono::system_clock::now(); 
    omp_set_num_threads(4); 
#pragma omp parallel for 
    for (int i = 0; i < elementsNum; i++) 
    { 
     unsigned int* elems = &elements[3*i]; 
     for (unsigned int j = 0; j < 3; j++) 
     { 
      //in here set mutex for row with index = elems[j]; 
      boost::lock_guard<boost::mutex> lockup(mutexes[i]); 
      double res = 0.0; 
      for (unsigned int k = 0; k < 3; k++) 
      { 
       res += ActionWithNumber(matrix[elems[j]*elementsNum + elems[k]]); 
      } 
      for (unsigned int k = 0; k < 3; k++) 
      { 
       matrix[elems[j]*elementsNum + elems[k]] = res; 
      } 
     } 
    } 
    end = boost::chrono::system_clock::now(); 
    boost::chrono::duration<double> elapsed_time = end - start; 

    delete[] elements; 
    delete[] matrix; 
    delete[] mutexes; 
    return elapsed_time.count(); 
} 

int main() 
{ 
    double sum = 0.0; 
    const u32 steps = 5; 
    for (u32 i = 0; i < steps; i++) 
    { 
     sum += RealAppTest(); 
    } 
    sum /= (double)steps; 
    std::cout<<"Elapsed time = " << sum; 
    return 0; 
} 
+0

프로그램이 실제로 25 초 동안 실행됩니까? (시계를 볼 때) –

+0

예. 나는 watch time을 위해 boost :: chrono :: system_clock을 사용한다. –

+1

이러한 성능 고려 사항에 대해 시스템의 컴파일러 옵션 및 하드웨어 구성은 물론 [mcve]와 같이 문제를 이해하고 재현 할 수있는 자세한 정보를 지정해야합니다. 또한 clang 3.7은 OpenMP 3.1 만 지원하며, OpenMP 5.0은 아직 최종 버전이 아닙니다. VS2010은 확실히 그것을 구현하지 않습니다. – Zulan

답변

1

당신은 스레딩/동기화 기본의 두 가지 세트 결합하고 - 컴파일러에 내장 런타임 시스템을 가지고있다 OpenMP를을, 수동으로 표준 : : 뮤텍스와 POSIX 뮤텍스를 생성. 일부 컴파일러/OS 조합과의 상호 운용성 문제가 있다는 것은 놀라운 일이 아닙니다.

저속한 경우에는 OpenMP 런타임이 상위 레벨의 진행중인 OpenMP 스레딩 작업과 수동 뮤텍스간에 상호 작용이 없음을 확인하고, 긴밀한 루프 내에서 그렇게하면 극적인 감속.

OpenMP의 프레임 워크에서 뮤텍스 같은 행동을 위해, 우리가 사용할 수있는 임계 영역 :

#pragma omp parallel for 
for (int i = 0; i < len; i++) 
{ 
    //... 
    // replacing this: std::lock_guard<std::mutex> scoped(_mutex); 
    #pragma omp critical 
    sum += c[i]; 
} 

또는 명시 적 잠금 :

omp_lock_t sumlock; 
omp_init_lock(&sumlock); 
#pragma omp parallel for 
for (int i = 0; i < len; i++) 
{ 
    //... 
    // replacing this: std::lock_guard<std::mutex> scoped(_mutex); 
    omp_set_lock(&sumlock); 
    sum += c[i]; 
    omp_unset_lock(&sumlock); 
} 
omp_destroy_lock(&sumlock); 

우리가 얻을 훨씬 더 합리적인 타이밍 :

$ time ./openmp-original 
real 1m41.119s 
user 1m15.961s 
sys 1m53.919s 

$ time ./openmp-critical 
real 0m16.470s 
user 1m2.313s 
sys 0m0.599s 

$ time ./openmp-locks 
real 0m15.819s 
user 1m0.820s 
sys 0m0.276s 

업데이트 됨 : n 뮤텍스와 정확히 같은 방법으로 openmp 잠금 배열을 만듭니다.

omp_lock_t sumlocks[elementsNum]; 
for (unsigned idx=0; idx<elementsNum; idx++) 
    omp_init_lock(&(sumlocks[idx])); 

//... 
#pragma omp parallel for 
for (int i = 0; i < elementsNum; i++) 
{ 
    unsigned int* elems = &elements[3*i]; 
    for (unsigned int j = 0; j < 3; j++) 
    { 
     //in here set mutex for row with index = elems[j]; 
     double res = 0.0; 
     for (unsigned int k = 0; k < 3; k++) 
     { 
      res += ActionWithNumber(matrix[elems[j]*elementsNum + elems[k]]); 
     } 
     omp_set_lock(&(sumlocks[i])); 
     for (unsigned int k = 0; k < 3; k++) 
     { 
      matrix[elems[j]*elementsNum + elems[k]] = res; 
     } 
     omp_unset_lock(&(sumlocks[i])); 
    } 
} 
for (unsigned idx=0; idx<elementsNum; idx++) 
    omp_destroy_lock(&(sumlocks[idx])); 
+0

나는 다음 게시물에서 당신에게 대답했습니다. –

+0

명시 적 잠금의 요점은 무엇입니까? 나는 그들을 사용한 적이 없다. 나는 단지'비판적'을 사용한다. 명백한 잠금이 더 나은 선택이 될 수있는 예를 들어 줄 수 있습니까? –

+0

I 설명 게시물의 끝에 내 작품 케이스. 나는이 경우 중요한 부분을 어떻게 사용하는지 이해하지 못한다. 테스트에서 나는 또한 임계 값을 사용했지만 뮤텍스를 사용할 때보 다 느린 속도를 보였다 (Win 머신에서). –