最小化嵌套循环中对std :: max的调用次数

时间:2013-05-21 07:46:36

标签: parallel-processing tbb processing-efficiency

我试图在内循环中减少对std :: max的调用次数,因为我已经调用了数百万次(毫不夸张!)并且这使得我的并行代码运行速度比顺序代码慢。基本思想(是的,这个IS用于赋值)是代码计算某个网格点的温度,迭代迭代,直到最大变化不超过某个非常小的数字(例如0.01)。新温度是直接在其上方,下方和旁边的单元格中的温度的平均值。因此,每个单元格都有不同的值,我想返回给定网格块的任何单元格中的最大更改。

我已经让代码正常工作,但速度很慢,因为我在内循环中对std :: max进行了大量(过度)的调用,并且它已经过了O(N * N)。我使用了1D域分解

注意:tdiff并不依赖于矩阵中的内容

缩减函数的输入是lambda函数的结果

diff是1次迭代中网格块中单个单元格的最大变化

阻止范围在代码

中已定义

t_new是该网格点的新温度,t_old是旧网格

max_diff = parallel_reduce(range, 0.0,
        //lambda function returns local max
        [&](blocked_range<size_t> range, double diff)-> double
        {
            for (size_t j = range.begin(); j<range.end(); j++)
            {
                for (size_t i = 1; i < n_x-1; i++)
                {
                    t_new[j*n_x+i]=0.25*(t_old[j*n_x+i+1]+t_old[j*n_x+i-1]+t_old[(j+1)*n_x+i]+t_old[(j-1)*n_x+i]);
                    tdiff = fabs(t_old[j*n_x+i] - t_new[j*n_x+i]);
                    diff = std::max(diff, tdiff);
                }   
            }
            return diff;    //return biggest value of tdiff for that iteration - once per 'i'
        },
        //reduction function - takes in all the max diffs for each iteration, picks the largest
        [&](double a, double b)-> double
        {
            convergence = std::max(a,b);
            return convergence;
        }
    );

如何让我的代码更有效率?我想减少对std :: max的调用,但需要保持正确的值。使用gprof我得到:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 61.66      3.47     3.47  3330884     0.00     0.00  double const& std::max<double>(double const&, double const&)
 38.03      5.61     2.14     5839     0.37     0.96  _ZZ4mainENKUlN3tbb13blocked_rangeImEEdE_clES1_d

ETA:61.66%的时间用于执行我的代码是在std :: max调用上,它调用超过300万次。为lambda函数的每个输出调用reduce函数,因此减少lambda函数中对std :: max的调用次数也会减少对reduce函数的调用次数

2 个答案:

答案 0 :(得分:1)

首先,我希望将std::max内联到其调用者中,因此gprof将其指向一个单独的热点是可疑的。你可以分析调试配置吗?

另外,我不认为std::max是罪魁祸首。除非在其实现中启用了一些特殊检查,否则我认为它应该等同于(diff<tdiff)?tdiff:diff。由于std :: max的一个参数是你更新的变量,你可以试试if (tdiff>diff) diff = tdiff;,但我怀疑它会给你带来多少(也许编译器可以自己做这样的优化)。

最有可能的是,std::max被突出显示为sampling skid的结果;即真正的热点在std::max以上的计算中,这是完全合理的,因为更多的工作和对非本地数据(阵列)的访问可能具有更长的延迟,特别是如果相应的位置不在CPU缓存中

根据网格中行(n_x)的大小,按行处理它可能效率低,缓存方式。最好尽可能多地重用t_old中的数据,而这些数据都在缓存中。按行处理,您要么根本不重复使用t_old中的点,直到下一行(对于i+1i-1点)或仅重复使用一次(对于两个邻居)在同一行)。更好的方法是通过矩形块处理网格,这有助于重用高速缓存中的热门数据。使用TBB,这样做的方法是使用blocked_range2d。它只需要对代码进行最小的更改;基本上,改变范围类型和lambda内部的两个循环:外循环和内循环应分别迭代range.rows()range.cols()

答案 1 :(得分:0)

我最终使用了parallel_for:

parallel_for(range, [&](blocked_range<size_t> range)
        {
            double loc_max = 0.0;
            double tdiff;
            for (size_t j = range.begin(); j<range.end(); j++)
            {
                for (size_t i = 1; i < n_x-1; i++)
                {
                    t_new[j*n_x+i]=0.25*(t_old[j*n_x+i+1]+t_old[j*n_x+i-1]+t_old[(j+1)*n_x+i]+t_old[(j-1)*n_x+i]);
                    tdiff = fabs(t_old[j*n_x+i] - t_new[j*n_x+i]); 
                    loc_max = std::max(loc_max, tdiff); 
                }   
            }
            //reduction function - takes in all the max diffs for each iteration, picks the largest
            {
                max_diff = std::max(max_diff, loc_max);
            }
        }
    );

现在我的代码在不到2秒的时间内运行8000x8000网格: - )