c++ - 多线程程序中映射模式的性能低于预期(4 倍加速比 8 倍)

标签 c++ multithreading performance tbb hyperthreading

我刚开始进行多线程编程,所以如果以下内容看起来很明显,请原谅。我正在将多线程添加到图像处理程序中,但加速并不完全符合我的预期。

我目前在具有超线程 (8) 的 4 物理处理器 cpu 上获得了 4 倍的加速,所以我想知道这种加速是否是预期的。我唯一能想到的是,如果单个物理 CPU 的两个超线程必须共享某种内存总线,这可能有意义。

作为多线程的新手,考虑到所有内存都分配在 RAM 中,我不太清楚这是否会被视为 I/O 绑定(bind)程序(我知道我的操作系统的虚拟内存管理器将决定分页从堆中输入/输出这个假设的内存量)我的机器有 16Gb 的 RAM,以防它帮助确定分页/交换是否是一个问题。

我已经使用 QThreadPool 和 tbb::parallel_for 编写了一个测试程序来展示串行情况和两个并行情况

如您所见,当前程序除了将假设的图像从黑色设置为白色外没有任何实际操作,它是故意在对图像应用任何实际操作之前知道基线是什么。

如果我对大约 8 倍加速的追求在这种处理算法中是失败的原因,我附上该程序希望有人能解释我。请注意,我对 SIMD 等其他类型的优化不感兴趣,因为我真正关心的不仅仅是让它更快,而是使用纯多线程使其更快,而不涉及 SSE 或处理器缓存级别优化。

#include <iostream>
#include <sys/time.h>

#include <vector>
#include <QThreadPool>
#include "/usr/local/include/tbb/tbb.h"

#define LOG(x) (std::cout << x << std::endl)

struct col4
{
    unsigned char r, g, b, a;
};

class QTileTask : public QRunnable
{
public:
    void run()
    {
        for(uint32_t y = m_yStart; y < m_yEnd; y++)
        {
            int rowStart = y * m_width;
            for(uint32_t x = m_xStart; x < m_xEnd; x++)
            {
                int index = rowStart + x;
                m_pData[index].r = 255;
                m_pData[index].g = 255;
                m_pData[index].b = 255;
                m_pData[index].a = 255;
            }
        }
    }

    col4*          m_pData;
    uint32_t       m_xStart;
    uint32_t       m_yStart;
    uint32_t       m_xEnd;
    uint32_t       m_yEnd;
    uint32_t       m_width;
};

struct TBBTileTask
{
    void operator()()
    {
        for(uint32_t y = m_yStart; y < m_yEnd; y++)
        {
            int rowStart = y * m_width;
            for(uint32_t x = m_xStart; x < m_xEnd; x++)
            {
                int index = rowStart + x;
                m_pData[index].r = 255;
                m_pData[index].g = 255;
                m_pData[index].b = 255;
                m_pData[index].a = 255;
            }
        }
    }

    col4*          m_pData;
    uint32_t       m_xStart;
    uint32_t       m_yStart;
    uint32_t       m_xEnd;
    uint32_t       m_yEnd;
    uint32_t       m_width;
};

struct TBBCaller
{
    TBBCaller(std::vector<TBBTileTask>& t)
        : m_tasks(t)
    {}

    TBBCaller(TBBCaller& e, tbb::split)
        : m_tasks(e.m_tasks)
    {}

    void operator()(const tbb::blocked_range<size_t>& r) const
    {
        for (size_t i=r.begin();i!=r.end();++i)
            m_tasks[i]();
    }

    std::vector<TBBTileTask>& m_tasks;
};

inline double getcurrenttime( void )
{
    timeval t;
    gettimeofday(&t, NULL);
    return static_cast<double>(t.tv_sec)+(static_cast<double>(t.tv_usec) / 1000000.0);
}

char* getCmdOption(char ** begin, char ** end, const std::string & option)
{
    char ** itr = std::find(begin, end, option);
    if (itr != end && ++itr != end)
    {
        return *itr;
    }
    return 0;
}

bool cmdOptionExists(char** begin, char** end, const std::string& option)
{
    return std::find(begin, end, option) != end;
}

void baselineSerial(col4* pData, int resolution)
{
    double t = getcurrenttime();
    for(int y = 0; y < resolution; y++)
    {
        int rowStart = y * resolution;
        for(int x = 0; x < resolution; x++)
        {
            int index = rowStart + x;
            pData[index].r = 255;
            pData[index].g = 255;
            pData[index].b = 255;
            pData[index].a = 255;
        }
    }
    LOG((getcurrenttime() - t) * 1000 << " ms. (Serial)");
}

void baselineParallelQt(col4* pData, int resolution, uint32_t tileSize)
{
    double t = getcurrenttime();

    QThreadPool pool;
    for(int y = 0; y < resolution; y+=tileSize)
    {
        for(int x = 0; x < resolution; x+=tileSize)
        {
            uint32_t xEnd = std::min<uint32_t>(x+tileSize, resolution);
            uint32_t yEnd = std::min<uint32_t>(y+tileSize, resolution);

            QTileTask* t = new QTileTask;
            t->m_pData = pData;
            t->m_xStart = x;
            t->m_yStart = y;
            t->m_xEnd = xEnd;
            t->m_yEnd = yEnd;
            t->m_width = resolution;
            pool.start(t);
        }
    }
    pool.waitForDone();
    LOG((getcurrenttime() - t) * 1000 << " ms. (QThreadPool)");
}

void baselineParallelTBB(col4* pData, int resolution, uint32_t tileSize)
{
    double t = getcurrenttime();

    std::vector<TBBTileTask> tasks;
    for(int y = 0; y < resolution; y+=tileSize)
    {
        for(int x = 0; x < resolution; x+=tileSize)
        {
            uint32_t xEnd = std::min<uint32_t>(x+tileSize, resolution);
            uint32_t yEnd = std::min<uint32_t>(y+tileSize, resolution);

            TBBTileTask t;
            t.m_pData = pData;
            t.m_xStart = x;
            t.m_yStart = y;
            t.m_xEnd = xEnd;
            t.m_yEnd = yEnd;
            t.m_width = resolution;
            tasks.push_back(t);
        }
    }

    TBBCaller caller(tasks);
    tbb::task_scheduler_init init;
    tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), caller);

    LOG((getcurrenttime() - t) * 1000 << " ms. (TBB)");
}

int main(int argc, char** argv)
{
    int resolution = 1;
    uint32_t tileSize = 64;

    char * pResText = getCmdOption(argv, argv + argc, "-r");
    if (pResText)
    {
        resolution = atoi(pResText);
    }

    char * pTileSizeChr = getCmdOption(argv, argv + argc, "-b");
    if (pTileSizeChr)
    {
        tileSize = atoi(pTileSizeChr);
    }

    if(resolution > 16)
        resolution = 16;

    resolution = resolution << 10;

    uint32_t tileCount = resolution/tileSize + 1;
    tileCount *= tileCount;

    LOG("Resolution: " << resolution << " Tile Size: "<< tileSize);
    LOG("Tile Count: " << tileCount);

    uint64_t pixelCount = resolution*resolution;
    col4* pData = new col4[pixelCount];

    memset(pData, 0, sizeof(col4)*pixelCount);
    baselineSerial(pData, resolution);

    memset(pData, 0, sizeof(col4)*pixelCount);
    baselineParallelQt(pData, resolution, tileSize);

    memset(pData, 0, sizeof(col4)*pixelCount);
    baselineParallelTBB(pData, resolution, tileSize);

    delete[] pData;

    return 0;
}

最佳答案

是的,预计 4 倍加速。 Hypertreading 是一种在硬件中实现的时间共享,因此如果一个线程用完了核心上所有可用的超标量管道,您就不能期望从中受益,就像您的情况一样。另一个线程必然要等待。

如果您的内存总线带宽因运行在少于可用内核总数的线程而饱和,则您可以预期甚至更低的加速。如果您有太多内核,通常会发生这种情况,例如这个问题:

Why doesn't this code scale linearly?

关于c++ - 多线程程序中映射模式的性能低于预期(4 倍加速比 8 倍),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31865073/

相关文章:

c++ - 返回对绑定(bind)到临时的引用参数的引用时的悬空引用

c++ - 如何在 x86 程序中以编程方式获取 Nvidia 驱动程序版本?

c++ - 没有获得正确的阴影位置,基于 CPU 的光线追踪

java - 实时更新 ListView JavaFX

python - 提高 numpy 三角函数运算的性能

c++ - 如何从 CEdit 控件中获取文本

C++11 多线程 : State of thread after execution

JavaFX 应用程序在使用多线程时出现滞后

python - 有效地找到由 numpy 数组的索引分割的子数组的 cumsum

ruby-on-rails - 是否有任何工具可以监控 Rails 中 Puma 进程排队的性能?