我试图在内循环中减少对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函数的调用次数
答案 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+1
和i-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网格: - )