【问题标题】:tbb::parallel_reduce vs tbb::combinable vs tbb::enumerable_thread_specifictbb::parallel_reduce vs tbb::combinable vs tbb::enumerable_thread_specific
【发布时间】:2016-08-22 15:09:55
【问题描述】:

我想浏览一张图片并处理一些关于元素顺序的特定值。该图像有一个 unsigned char* 数组,其中包含一个掩码(如果应处理像素,则为 255,否则为 0)和一个包含像素值的 unsigned short* 数组。

我用 tbb 实现了三种不同的方法,并在掩码数组中使用了一个 for 循环,并从循环变量中计算了 x,y 坐标:x = i%width; y = i/width;。如果像素可见,我想使用Eigen 转换点。 vector4d 是一个 std::vector<std::array<double,4>> 来存储积分。

这是我使用 tbb 的三个实现:

1. tbb::combinabletbb::parallel_for

void Combinable(int width, int height, unsigned char* mask,unsigned short*  pixel){ 
    MyCombinableType.clear();
    MyCombinableType.local().reserve(width*height);
    tbb::parallel_for( tbb::blocked_range<int>(0, width*height),
        [&](const tbb::blocked_range<int> &r) 
    {       
        vector4d& local = MyCombinableType.local(); 
        const size_t end = r.end(); 
        for (int i = r.begin(); i != end; ++i)
        {
            if(mask[i]!=0)
            {                                       
                array4d arr = {i%width,i/width,(double)pixel[i],1}; 
                //Map with Eigen and transform
                local.push_back(arr);           
            }
        }
    });

    vector4d idx = MyCombinableType.combine(
        []( vector4d x, vector4d y) 
    {               
        std::size_t n = x.size();
        x.resize(n + y.size());
        std::move(y.begin(), y.end(), x.begin() + n);
        return x;
    });
}

2。 tbb::enumerable_thread_specifictbb::parallel_for:

void Enumerable(int width, int height, unsigned char* mask,unsigned short*  pixel){
    MyEnumerableType.clear();
    MyEnumerableType.local().reserve(width*height);
    tbb::parallel_for( tbb::blocked_range<int>(0, width*height),
        [&](const tbb::blocked_range<int> &r) 
    {
        enumerableType::reference local = MyEnumerableType.local();
        for (int i = r.begin(); i != r.end(); ++i)
        {
            if(mask[i]!=0)
            {
                array4d arr = {i%width,i/width,(double)pixel[i],1}; 
                //Map with Eigen and transform
                local.push_back(arr);               

            }
        }
    });

    vector4d idx = MyEnumerableType.combine(
        [](vector4d x, vector4d y) 
    {           
        std::size_t n = x.size();
        x.resize(n + y.size());
        std::move(y.begin(), y.end(), x.begin() + n);
        return x;
    });
}

3. tbb::parallel_reduce:

void Reduce(int width, int height, unsigned char* mask,unsigned short*  pixel){
    vector4d idx = tbb::parallel_reduce(
        tbb::blocked_range<int>(0, width*height ),vector4d(),
            [&](const tbb::blocked_range<int>& r, vector4d init)->vector4d 
        {
            const size_t end = r.end(); 
            init.reserve(r.size());
            for( int i=r.begin(); i!=end; ++i )
            {   
                if(mask[i]!=0)
                {               
                    array4d arr = {i%width,i/width,(double)pixel[i],1}; 
                    //Map with Eigen and transform
                    init.push_back(arr);            
                }
            }
            return init;
        },
        []( vector4d x,vector4d y )
        {
            std::size_t n = x.size();
            x.resize(n + y.size());
            std::move(y.begin(), y.end(), x.begin() + n);           
            return x;
        }
    );  
}

我将三个版本的运行时间与串行实现进行了比较。数组有 8400000 个元素,每个算法重复 100 次。结果是:

  • 串行:~170ms
  • 可枚举:~118ms
  • 可组合:~116ms
  • 减少:~720ms

我假设combine 语句是这里的瓶颈。我究竟做错了什么?为什么parallel_reduce 这么慢?请帮忙!

【问题讨论】:

  • 您将 vector4d 按值传递给 lambda 表达式。 vector4d 的复制构造函数有多贵?
  • 我更新了我的问题。我完全忘了提到vector4d,谢谢你的提示!
  • 在下面查看我更好的答案,但对您的代码的小改进是:init.reserve(init.size() + r.size());

标签: c++ multithreading image-processing tbb eigen3


【解决方案1】:

您可以在此处应用一些优化。

  1. 避免过度复制:改为传递const vector4d&amp;,在任何地方使用[&amp;] lambdas。
  2. 在堆栈上使用临时vector4d,而不是调整参数之一并将其用于返回语句。
  3. 一般情况下,使用blocked_range2d 而不是计算x = i%width; y = i/width。这不仅优化了过多的计算,而且更重要的是,它优化了可能提高缓存使用率的缓存访问模式(虽然在这种情况下不是)。

【讨论】:

    【解决方案2】:

    您正在使用parallel_reduce 的函数形式,请尝试使用更有效的命令形式。不幸的是,它不能使用 lambdas 调用,您必须定义一个 Body 类:

    https://www.threadingbuildingblocks.org/docs/help/reference/algorithms/parallel_reduce_func.html

    它应该最大限度地减少减少期间制作的 vector4d 副本的数量。 vector4d 应该是 Body 类的成员,以便它可以被多个范围重复使用和附加,而不是为每个细分范围构造和合并唯一的 vector4d。

    (注意:拆分构造函数不应该复制vector4d成员的内容,注意value在上面的英特尔示例中总是初始化为0。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-15
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多