首页 > 解决方案 > 并发程序中向量算术运算符重载的最佳实践

问题描述

问题上下文

我目前正在使用 OpenMP 并行编程模型分析和改进用 C++ 编写的计算密集型并发应用程序的性能。我使用分析工具看到代码的特定并行区域将频率降至 200MHz,这意味着大量周期花费在系统时间或 CPU 空闲上。我已经确定此问题的原因是并发执行大量内存分配,导致分配器同步线程并浪费大量时间等待。

但是,这些内存分配vector<double>是在所讨论的并行循环期间大量使用的运算符重载的结果(从现在开始感兴趣的区域)。运算符重载的作用如下:

std::vector<double> operator+( const std::vector<double>& v1 , const std::vector<double>& v2 )
{
 std::vector<double> v = v1;
 for( unsigned int i=0; i < v1.size() ; i++ )
 { v[i] += v2[i]; }
 return v; 
}

这只是一个示例,其他算术运算符以及向量和标量(double类型)的操作也是如此。如您所见,一个新的向量被初始化,然后作为操作的结果返回。这会导致至少一个malloc和一个free。但正如我所说,这在感兴趣区域的一次迭代中被多次调用,并且这个循环在大量并行线程(最多 48 个)上运行大量迭代。此操作调用的一个示例如下:

std::vector<double> corner_point= 0.5*(my_voxel_center+other_voxel_center);

在这种情况下,两个操作一个接一个地完成(+向量,然后*向量和标量),然后将结果分配给新创建的向量。

问题

所以,我的问题如下:正如我们已经看到它的性能有多糟糕,这应该是运算符重载的最佳实践,特别是对于像这样的类型vector<>,以避免每次调用它时分配和释放一个新向量?有没有更好的写法?

我已阅读“https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading”帖子寻求帮助,但没有具体的评论重载函数内部的内存使用,以及它们如何在并发应用程序上执行。

我知道,在这种情况下,也许没有其他方法可以解决这个问题:我应该把注意力集中在哪里来解决这个问题?我的想法是:

标签: c++memory-managementparallel-processingoperator-overloading

解决方案


下面的建议与运算符重载几乎没有关系,而是与一般向量的使用有关。

有几点需要改进:

reserve()首先,在复制任何内容之前,您绝对应该使用, 。

std::vector<double> operator+( const std::vector<double>& v1 , const std::vector<double>& v2 )
{
 std::vector<double> v;
 v.reserve(v1.size() + v2.size());
 v = v1;
 for( unsigned int i=0; i < v1.size() ; i++ )
 { v[i] += v2[i]; }
 return v; 
}

这样,每次操作员调用您将有 1 个分配,而不是几个(至少 1 个,如果v1很小并且v2很大,最多很多)。

然后我猜 range 版本的insert()性能可能比循环更好,但这可能不是真的,应该进行测试(至少它不应该比循环差)。

std::vector<double> operator+( const std::vector<double>& v1 , const std::vector<double>& v2 )
{
 std::vector<double> v;
 v.reserve(v1.size() + v2.size());
 v.insert(v.end(), v1.begin(), v1.end());
 v.insert(v.end(), v2.begin(), v2.end());
 return v; 
}

也许它也值得考虑是否v1必须保持不变。如果没有,那么可能值得重载operator +=并使用它。


当然,请确保尝试不同的优化级别,有时-Os-O2可能比-O3


推荐阅读