C++ OpenMP:循环通过';标准::地图';基准(动态调度)

C++ OpenMP:循环通过';标准::地图';基准(动态调度),c++,multithreading,parallel-processing,multiprocessing,openmp,C++,Multithreading,Parallel Processing,Multiprocessing,Openmp,我必须循环执行std::map,每次迭代中必须完成的工作具有以下属性: 在每次迭代中,工作量是不同的 线程之间不需要任何同步 看起来是动态调度的完美场景,不是吗 然而,就OpenMP的循环并行化而言,非随机访问迭代器(如std::maphas)是臭名昭著的。对我来说,这段特定代码的性能将是至关重要的,因此为了寻找最有效的解决方案,我创建了以下基准: #include <omp.h> #include <iostream> #include <map> #in

我必须循环执行
std::map
,每次迭代中必须完成的工作具有以下属性:

  • 在每次迭代中,工作量是不同的
  • 线程之间不需要任何同步
  • 看起来是动态调度的完美场景,不是吗

    然而,就OpenMP的循环并行化而言,非随机访问迭代器(如
    std::map
    has)是臭名昭著的。对我来说,这段特定代码的性能将是至关重要的,因此为了寻找最有效的解决方案,我创建了以下基准:

    #include <omp.h>
    
    #include <iostream>
    #include <map>
    #include <vector>
    
    #define COUNT 0x00006FFF
    
    #define UNUSED(variable) (void)(variable)
    
    using std::map;
    using std::vector;
    
    void test1(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
    
      map<int, vector<int> >::iterator iterator = m.begin();
    
    #pragma omp parallel
    #pragma omp for schedule(dynamic, 1) nowait
      for (size_t i = 0; i < m.size(); ++i) {
        vector<int>* v;
    #pragma omp critical
        v = &iterator->second;
    
        for (size_t j = 0; j < v->size(); ++j) {
          (*v)[j] = j;
        }
    
    #pragma omp critical
        iterator++;
      }
    
      printf("Test #1: %f s\n", (omp_get_wtime() - time));
    }
    
    void test2(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
    
    #pragma omp parallel
      {
        for (map<int, vector<int> >::iterator i = m.begin(); i != m.end(); ++i) {
    #pragma omp single nowait
          {
            vector<int>& v = i->second;
    
            for (size_t j = 0; j < v.size(); ++j) {
              v[j] = j;
            }
          }
        }
      }
    
      printf("Test #2: %f s\n", (omp_get_wtime() - time));
    }
    
    void test3(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
    
    #pragma omp parallel
      {
        int thread_count = omp_get_num_threads();
        int thread_num = omp_get_thread_num();
        size_t chunk_size = m.size() / thread_count;
        map<int, vector<int> >::iterator begin = m.begin();
        std::advance(begin, thread_num * chunk_size);
        map<int, vector<int> >::iterator end = begin;
        if (thread_num == thread_count - 1)
          end = m.end();
        else
          std::advance(end, chunk_size);
    
        for (map<int, vector<int> >::iterator i = begin; i != end; ++i) {
          vector<int>& v = i->second;
    
          for (size_t j = 0; j < v.size(); ++j) {
            v[j] = j;
          }
        }
      }
    
      printf("Test #3: %f s\n", (omp_get_wtime() - time));
    }
    
    int main(int argc, char** argv) {
      UNUSED(argc);
      UNUSED(argv);
    
      map<int, vector<int> > m;
    
      for (int i = 0; i < COUNT; ++i) {
        m[i] = vector<int>(i);
      }
    
      test1(m);
      test2(m);
      test3(m);
    }
    
    我之所以发布这个问题,是因为我发现这些结果很奇怪,而且完全出乎意料:

  • 预期测试2最快,因为它不像测试1那样使用临界截面
  • 预期测试#3的速度最慢,因为它实际上没有利用动态调度,而是依赖于作业的静态分布(这是手动完成的)
  • 没想到测试2和测试3大致相当,有时甚至更糟
  • 问题是:

  • 我遗漏了什么吗
  • 你能解释一下测试结果吗
  • 你对并行化有更好的理解吗
  • 你对并行化有更好的理解吗
  • 您可以尝试模仿OpenMP循环的
    schedule(static,1)
    ,即线程使用
    thread\u count
    步长处理迭代,而不是处理大量连续迭代。代码如下:

    void test4(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
    
    #pragma omp parallel
      {
        int thread_count = omp_get_num_threads();
        int thread_num = omp_get_thread_num();
        size_t map_size = m.size();
        map<int, vector<int> >::iterator it = m.begin();
        std::advance(it, thread_num);
    
        for (int i = thread_num; i < map_size; i+=thread_count) {
          vector<int>& v = it->second;
    
          for (size_t j = 0; j < v.size(); ++j) {
            v[j] = j;
          }
    
          if( i+thread_count < map_size ) std::advance(it, thread_count);
        }
      }
    
      printf("Test #4: %f s\n", (omp_get_wtime() - time));
    }
    

    在循环中,线程决定其本地迭代器在映射上的前进量。线程首先以原子方式递增计数器并获取其上一个值,从而获得迭代索引,然后根据新索引和上一个索引之间的差值使迭代器前进。循环重复,直到计数器增加到地图大小以上。

    您是否尝试过使用无序地图进行基准测试?@Leeor,我为什么要这样做?在这种情况下,它不会改变任何东西。这个问题与数据结构无关。它是关于并行化策略的。最后,我对这个特定任务的
    无序图
    不感兴趣。
    
    void test4(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
    
    #pragma omp parallel
      {
        int thread_count = omp_get_num_threads();
        int thread_num = omp_get_thread_num();
        size_t map_size = m.size();
        map<int, vector<int> >::iterator it = m.begin();
        std::advance(it, thread_num);
    
        for (int i = thread_num; i < map_size; i+=thread_count) {
          vector<int>& v = it->second;
    
          for (size_t j = 0; j < v.size(); ++j) {
            v[j] = j;
          }
    
          if( i+thread_count < map_size ) std::advance(it, thread_count);
        }
      }
    
      printf("Test #4: %f s\n", (omp_get_wtime() - time));
    }
    
    void test5(map<int, vector<int> >& m) {
      double time = omp_get_wtime();
      int count = 0;
    #pragma omp parallel shared(count)
      {
        int i;
        int i_old = 0;
        size_t map_size = m.size();
        map<int, vector<int> >::iterator it = m.begin();
    
    #pragma omp atomic capture
        i = count++;
    
        while (i < map_size) {
          std::advance(it, i-i_old);
          vector<int>& v = it->second;
    
          for (size_t j = 0; j < v.size(); ++j) {
            v[j] = j;
          }
    
          i_old = i;
    #pragma omp atomic capture
          i = count++;
        }
      }
    
      printf("Test #5: %f s\n", (omp_get_wtime() - time));
    }