多线程函数性能比单线程差

时间:2019-02-27 21:21:33

标签: c++ multithreading openmp

我编写了一个在单个线程上运行的update()函数,然后编写了下面的函数updateMP(),该函数除了在两个线程中将我的两个for循环中的工作划分为一些线程外,还执行相同的操作:

void GameOfLife::updateMP()
{
    std::vector<Cell> toDie;
    std::vector<Cell> toLive;

#pragma omp parallel
    {
        // private, per-thread variables
        std::vector<Cell> myToDie;
        std::vector<Cell> myToLive;

#pragma omp for
        for (int i = 0; i < aliveCells.size(); i++) {
            auto it = aliveCells.begin();
            std::advance(it, i);

            int liveCount = aliveCellNeighbors[*it];
            if (liveCount < 2 || liveCount > 3) {
                myToDie.push_back(*it);
            }
        }

#pragma omp for
        for (int i = 0; i < aliveCellNeighbors.size(); i++) {
            auto it = aliveCellNeighbors.begin();
            std::advance(it, i);

            if (aliveCells.find(it->first) != aliveCells.end()) // is this cell alive?
                continue; // if so skip because we already updated aliveCells

            if (aliveCellNeighbors[it->first] == 3) {
                myToLive.push_back(it->first);
            }
        }

#pragma omp critical
        {
            toDie.insert(toDie.end(), myToDie.begin(), myToDie.end());
            toLive.insert(toLive.end(), myToLive.begin(), myToLive.end());
        }
    }

    for (const Cell& deadCell : toDie) {
        setDead(deadCell);
    }

    for (const Cell& liveCell : toLive) {
        setAlive(liveCell);
    }
}

我注意到它的性能比单线程update()差,并且随着时间的流逝,它看起来越来越慢。

我认为同时使用omp for可能会做错什么?我是OpenMP的新手,所以我仍在弄清楚如何使用它。

为什么我的多线程实现的性能会变差?

编辑:此处提供完整信息:https://github.com/k-vekos/GameOfLife/tree/hashing?files=1

2 个答案:

答案 0 :(得分:2)

  

为什么我的多线程实现的性能会变差?

经典问题:)

您只能遍历活着的细胞。这实际上很有趣。康威《生命游戏》的幼稚实现会观察每个单元。您的版本优化后的存活细胞数量少于死细胞,我认为这在游戏后期很常见。我不能从您的摘录中看出来,但是我认为当活细胞与死细胞的比率更高时,它可能会做一些多余的工作来权衡。

omp parallel的一个警告是,不能保证在并行段的进入/退出期间不会创建/销毁线程。它取决于实现。我似乎找不到有关MSVC实施的任何信息。如果有人知道,请增加体重。

所以这意味着您的线程可以在每个更新循环中创建/销毁,这是沉重的开销。为了使之物有所值,工作量应比间接费用贵几个数量级。

您可以分析/测量代码以确定开销和工作时间。它还应该可以帮助您了解真正的瓶颈在哪里。

Visual Studio有一个带有漂亮GUI的探查器。您需要使用发行版优化来编译代码,但还需要包含调试符号(默认发行版配置中不包含这些符号)。我没有研究如何手动设置,因为我通常使用CMake,它会自动设置RelWithDebInfo配置。

使用high_resolution_clock为难以使用分析器测量的部分计时。

如果可以使用C ++ 17,则它具有标准的并行for_each(ExecutionPolicy重载)。大多数算法标准功能都可以做到。 https://en.cppreference.com/w/cpp/algorithm/for_each。它们太新了,我几乎对它们一无所知(它们也可能和OpenMP一样有问题)。

  

似乎随着时间的流逝越来越慢。

可能是您没有清除其中一个媒介吗?

答案 1 :(得分:0)

首先,如果您想要任何性能,则必须在关键部分中尽可能少地进行工作。我将从更改以下内容开始:

std::vector<Cell> toDie;
std::vector<Cell> toLive;

std::vector<std::vector<Cell>> toDie;
std::vector<std::vector<Cell>> toLive;

然后,在关键部分,您可以执行以下操作:

toDie.push_back(std::move(myToDie));
toLive.push_back(std::move(myToLive));

可以说,向量的向量不是很可爱,但这可以防止在CS内部进行深度复制,这是不必要的时间消耗。

[更新] 恕我直言,如果您使用的是不连续的数据结构,那么至少在这种情况下,使用多线程是没有意义的。事实是,您将花费大部分时间等待高速缓存未命中,因为这是关联容器所做的事情,而很少做实际工作。 我不知道这个游戏如何运作。感觉就像是我必须对大量更新和渲染进行某些操作时,我将在“主”线程上尽快完成更新,并且为渲染器设置另一个(分离的)线程。然后,您可以在每次“更新”之后为渲染器提供结果,并在渲染时执行另一个更新。

此外,我绝对不是哈希专家,但是hash<int>()(k.x * 3 + k.y * 5)似乎是一种高冲突哈希。您当然可以尝试其他类似建议的here