簡體   English   中英

為什么OpenMP比順序程序慢一些以簡化操作?

[英]Why OpenMP is slower than a sequential program for a simple reduction?

我正在嘗試在數組中查找元素的總和,如下所示。 但是,令人驚訝的是,OpenMP實現比順序實現慢。 我嘗試了堆分配和堆棧分配的數組,並得到了相似的結果。 任何幫助是極大的贊賞。

#include <iostream>
#include <omp.h>
int main() {
  int N = 10000;
  int * ary = new int[N];
  for (int i = 0; i < N; i++) { input_file >> ary[i]; }
  int sum = 0;
  clock_t begin = clock();
  for (int i = 0; i < N; i++) { sum += ary[i]; }
  clock_t end = clock();
  cout << sum;
  double elapsed_time = double(end - begin) / CLOCKS_PER_SEC;
  sum = 0;
  begin = clock();
  #pragma omp parallel
  {
    int thread_id = omp_get_thread_num();
    int total_threads = omp_get_num_threads();
    int elem_per_thread = N / total_threads;
    int base = thread_id * elem_per_thread;
    int internal_sum = 0;
    for (int i = base; i < (base + elem_per_thread); i++) {
      internal_sum += ary[i];
    }
    #pragma omp critical
    {
      sum += internal_sum;
    }
  }
  end = clock();
  cout << sum;
  elapsed_time = double(end - begin) / CLOCKS_PER_SEC;    
}

順序程序需要5e-06 (s)才能完成,而並行程序則需要0.001733 (s)。 我正在使用g++ -std=c++11 main.cpp -fopenmp -O3 && ./a.out在Ubuntu 16.04上進行編譯

順序程序可以優化為無所事事。 這是因為唯一副作用是價值sum ,和值sum是不是在你的程序觀察。

使用OpenMP時,復雜的線程處理使編譯器無法意識到您沒有做任何事情。

避免這種情況的一種簡單方法是增加return sum; 現在,它顯示為可觀察到的退出代碼,因此無法優化計算。

現在,編譯器仍然可以自由地從不分配ary ,因為它可以證明ary[i]==i代表所有i ,並將讀取的ary[i]替換為i ,然后在編譯時計算出i的總和11000050005000 ,消除整個循環並使其sum=50005000仍需要零時間。

事先說明:
我相信處理“手動”划分循環的方式會適得其反(除非您想了解OpenMP的工作原理)。 這就是為什么我首先建議您對reduction操作使用更標准的方法。 您始終可以檢查它是否在性能方面得到相同的結果。
另一個omp_是,在整個代碼中使用omp_函數將無法在沒有-openmp選項的情況下對其進行編譯。

替補

因此,我使用了以下代碼:

標頭

#include <iostream>
#include <fstream>
#include <omp.h>
#include <cmath>
#include <chrono>
#include <iomanip>

一個非常簡單的添加操作即可測試功能

void test_simple(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
  std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
  start = std::chrono::system_clock::now();
  double local_sum = 0.0;
  #pragma omp parallel
  {
    #pragma omp for reduction(+:local_sum)
    for (long long int i = 0; i < N; i++) {
      local_sum += ary[i];
    }
  }
  sum = local_sum;
  end = std::chrono::system_clock::now();
  elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
                             (end-start).count();
}

具有復雜的CPU密集型操作符號的測試函數(x)atan(sqrt(cos(x)^ 2 + sin(0.5x)^ 2)

void test_intensive(long long int N, int * ary, double & sum, long long int & elapsed_milli)
{
  std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
  start = std::chrono::system_clock::now();
  double local_sum = 0.0;
  #pragma omp parallel
  {
    double c, s;
    #pragma omp for reduction(+:local_sum)
    for (long long int i = 0; i < N; i++) {
      c = cos(double(ary[i]));
      s = sin(double(ary[i])*0.5);
      local_sum += atan(sqrt(c*c+s*s));
    }
  }
  sum = local_sum;
  end = std::chrono::system_clock::now();
  elapsed_milli = std::chrono::duration_cast<std::chrono::microseconds>
                             (end-start).count();  
}

主功能

using namespace std;
int main() {
  long long int N = 1073741825,i;
  int * ary = new int[N];
  srand (0);
  for (i = 0; i < N; i++) { ary[i] = rand()-RAND_MAX/2; }
  double sum = 0.0;
  sum = 0.0;
  long long int  elapsed_milli;
  cout <<"#"<<setw(19)<<"N"<<setw(20)<<"µs"<< endl;
  for(i=128; i<N; i=i*2)
  {
      test_intensive(i, ary, sum, elapsed_milli);
      //test_simple(i, ary, sum, elapsed_milli);
      cout << setw(20)<<i<<setw(20)<<elapsed_milli << setw(20)<<sum<<endl;
  }
}

編譯(使用icpc)
順序(無OpenMP)版本使用以下命令進行編譯:

icpc test_omp.cpp -O3 --std=c++0x  

OpenMP(OpenMP)版本使用以下命令進行編譯:

icpc test_omp.cpp -O3 --std=c++0x -openmp

測量
時間測量與完成chrono使用high_precision_clock和我的機器上的極限精度為微秒,因此使用std::chrono::microseconds (無點尋找更高的精度)

簡單操作的圖形 (軸為對數刻度!)
CPU簡單

復雜操作的圖形 (軸為對數刻度!)
CPU密集型

得出結論

  • 第一次使用OpenMP時會發生偏移(第一個#pragma omp越過),因為必須將池線程設置在適當的位置。
    如果我們在首次進入test_函數(i = 128)時仔細研究“密集型案例”,則在OpenMP案例中的時間成本要比在No OpenMP案例中的時間成本高得多。 在第二次調用中(i = 256),我們沒有看到使用OpenMP的好處,但是時間安排是一致的。 CPU密集關閉
  • 我們可以看到,使用少量樣本就不會觀察到可伸縮性。 在簡單的測試案例中,這一點更加清楚。 換句話說,並行部分中的操作量必須足夠高,以使線程池管理所需的時間可以忽略 否則,將操作分為線程是沒有意義的。

  • 在這種情況下(使用我使用的處理器),最小樣本數大約為100000。但是,如果我使用256個線程,則肯定大約為6000000。

  • 但是,對於更多的CPU密集型操作,即使使用1000個樣本(使用我使用的處理器),使用OpenMP也會導致速度加快

摘要

  • 如果您使用OpenMP代碼,請嘗試使用#pragma omp parallel的簡單操作預先設置池線程 在您的測試案例中,設置花費了大部分時間。
  • 僅當並行化足夠占用CPU的功能時才使用OpenMP(這不是簡單的數組求和的情況……)。 例如,這就是為什么在嵌套循環中#pragma omp for應該始終位於最外層的“可能”循環中的原因。

正如Max Langhof和user463035818所建議的那樣,該程序受內存限制。 我更改了程序,以完成除累積以外的其他操作。 也就是說,我將sum += ary[i]更改為sum += (pow(ary[i], 1.1) + pow(ary[i], 1.2)) / 100000000.0並在並行程序中執行了相同的更改並測量時間。 並行程序的速度提高了2倍。 如果該程序受IO限制,我想我不能做很多事情來使它與OpenMP一起更快。 否則,請告訴我。

暫無
暫無

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

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