如何改进此代码以便运行多线程?

时间:2012-08-23 11:54:24

标签: c++ linux multithreading gcc openmp

我刚刚开始使用OpenMP指令来使用多个线程。然而,这段代码使用单线程版本运行得最快。在我看来,算法应该很好地扩展,因为计算是独立的。这里发生了什么事?我该如何改进代码?

#include <omp.h>

std::vector<Track> interpolateTracks(const std::vector<Track>& tracks,  double segmentLength) {
    typedef std::vector<Track>::const_iterator iterator;
    std::vector<Track> list;
    #pragma omp parallel shared(list, tracks, segmentLength)
    {
        std::vector<Track> local;
        iterator myBegin = threadBegin(tracks.begin(), tracks.end());
        iterator myEnd = threadEnd(tracks.begin(), tracks.end());
        for (iterator i = myBegin; i < myEnd; ++i) {
            const Track& t = *i;
            TrackInterpolator interpol(t);
            const Track& result = interpol.bySegmentLength(segmentLength);
            local.push_back(result);
        }
        #pragma omp critical
        {
            list.insert(list.end(), local.begin(), local.end());
            std::cout << "Done: " << omp_get_thread_num() << std::endl;
        }
    }
    return list;
}

函数beginThread(begin, end)endThread(begin,end)根据当前线程数和线程数返回beginend定义的范围的小块。

以下是他们的实施:

#include <omp.h>

template <class I>
I threadBegin(I begin, I end) {
    int part = omp_get_thread_num();
    int parts = omp_get_num_threads();
    double chunk = (end - begin)*1.0/parts;
    ptrdiff_t diff = (ptrdiff_t) (chunk*part);
    return begin + diff;
}

template <class I>
I threadEnd(I begin, I end) {
    //the end of i is the begin of i+1
    int part = omp_get_thread_num() + 1;
    int parts = omp_get_num_threads();
    if (part == parts) {
        return end;
    } else {
        double chunk = (end - begin)*1.0/parts;
        ptrdiff_t diff = (ptrdiff_t) (chunk*part);
        return begin + diff;
    }
}

我在具有16个内核的linux机器上运行代码。

不幸的是我只能访问有点过时的gcc((SUSE Linux)4.5.1 20101208),以防这可能是原因。

P.S。我的第一个版本在 critical 部分中使用了list.push_back(..)的并行for循环,这比在此处发布的变体更慢。

1 个答案:

答案 0 :(得分:1)

嗯,你的代码似乎是正确的,但是我看到了可能存在的性能问题:

  1. 关键部分当然是性能杀手,特别是如果计算不太昂贵和/或轨道矢量不是很大。
  2. 存储Track对象的事实意味着当您将它们从本地向量移动到最终向量时,必须复制它们。
  3. 你知道你的矢量的最终大小,但你动态地增长它们。
  4. threadBegin和threadEnd函数利用浮点运算和FP进行整数转换。特别是这些,以及进行等效整数运算要慢得多。
  5. 以下是我的建议:

    1. 在你的矢量中存储std :: unique_ptr。
    2. 将您的矢量预先分配到最终大小。
    3. 为了避免在最后需要一个关键部分,我看到两个选项: a)直接在最终数组中工作,但找到正确的块。由于它将预先分配,因此您无需保护它。 b)在局部向量中工作,但随后从线程中复制到预分配的最终向量的正确块。
    4. 使用整数数学计算您的块 - 您应该能够在fork之前执行大部分计算,然后只需更正最后一个块的大小。