C++ 如何提高OpenMP代码的性能?
我目前正在努力提高代码的并行性能,而且我还是OpenMP新手。我必须在一个大容器上迭代,在每次迭代中读取多个条目并将结果写入单个条目。下面是一个非常小的代码示例,说明了我正在尝试做的事情C++ 如何提高OpenMP代码的性能?,c++,multithreading,performance,openmp,C++,Multithreading,Performance,Openmp,我目前正在努力提高代码的并行性能,而且我还是OpenMP新手。我必须在一个大容器上迭代,在每次迭代中读取多个条目并将结果写入单个条目。下面是一个非常小的代码示例,说明了我正在尝试做的事情 data是指向数组的指针,其中存储了大量数据点。在并行区域之前,我创建了一个数组newData,因此可以将data用作只读,将newData用作只读,然后我将旧的数据扔掉,并使用newData进行进一步的计算。 据我所知,data和newData在线程之间共享,并且在并行区域内声明的所有内容都是私有的。 多线程
data
是指向数组的指针,其中存储了大量数据点。在并行区域之前,我创建了一个数组newData
,因此可以将data
用作只读,将newData
用作只读,然后我将旧的数据
扔掉,并使用newData
进行进一步的计算。
据我所知,data
和newData
在线程之间共享,并且在并行区域内声明的所有内容都是私有的。
多线程读取数据是否会导致性能问题
我使用#critical
为newData
的元素指定新值,以避免竞争条件。这是必要的吗?因为我只访问newData
的每个元素一次,而且从不通过多个线程访问
我也不确定日程安排。我是否必须指定我想要的是静态
还是动态
计划?既然所有线程都是相互独立的,我可以使用nowait
array *newData = new array;
omp_set_num_threads (threads);
#pragma omp parallel
{
#pragma omp for
for (int i = 0; i < range; i++)
{
double middle = (*data)[i];
double previous = (*data)[i-1];
double next = (*data)[i+1];
double new_value = (previous + middle + next) / 3.0;
#pragma omp critical(assignment)
(*newData)[i] = new_value;
}
}
delete data;
data = newData;
array*newData=新数组;
omp_设置_数量_线程(线程);
#pragma-omp并行
{
#pragma omp for
对于(int i=0;i
我知道在第一次和最后一次迭代中,上一次
和下一次
不能从数据
中读取,在实际代码中,这一点得到了解决,但对于这个最小的示例,您可以从数据
中多次读取
- 测量性能通常会受到电脑上其他任务的干扰,因此请进行多次测量并取其最小值(除非每次输入不同,然后取平均值并进行更多测量)
- 你真的需要双精度吗?浮动要快得多
我假设你想用一维数组做卷积或中值模糊。简单的回答是:坚持默认的日程安排策略,彻底摆脱关键问题
我可以告诉你,你是一个对并行性不感兴趣的新手,处理OpenMP指令有点混乱,比如nowait/private/reduce/critical/atomic/single等等。我认为你需要一本编写良好的教科书来澄清各种概念。如果您有扎实的知识,一个小时的OpenMP学习足以处理大多数日常编程 首先,消除所有不必要的依赖关系<代码>#pragma omp critical(assignment)不是必需的,因为
(*newData)
的每个索引在每个循环中只写入一次,因此不存在争用条件
您的代码现在可以如下所示:
#pragma omp parallel for
for (int i = 0; i < range; i++)
(*newData)[i] = ((*data)[i-1] + (*data)[i] + (*data)[i+1]) / 3.0;
assert(range % 4 == 0);
#pragma omp parallel for
for (int i = 0; i < range/4; i++) {
(*newData)[i*4+0] = ((*data)[i*4-1] + (*data)[i*4+0] + (*data)[i*4+1]) / 3.0;
(*newData)[i*4+1] = ((*data)[i*4+0] + (*data)[i*4+1] + (*data)[i*4+2]) / 3.0;
(*newData)[i*4+2] = ((*data)[i*4+1] + (*data)[i*4+2] + (*data)[i*4+3]) / 3.0;
(*newData)[i*4+3] = ((*data)[i*4+2] + (*data)[i*4+3] + (*data)[i*4+4]) / 3.0;
}
内存带限制:
对于非常简单的循环,请考虑以下内容。您必须加载多少内存,CPU将在多长时间内忙于处理它。您将加载大约1条缓存线,并计算一些解引用、一些指针加法、两个加法和一个除法。您达到的限制取决于您的CPU规格。
现在考虑缓存局部性。您能否修改代码以更好地利用缓存?如果一个线程在一次循环迭代中得到i=3,而在下一次循环迭代中得到i=7,则必须重新加载3(*数据)。但是如果从i=3变为i=4,则可能不必加载任何内容,因为(*data)[i+1]位于先前加载的缓存线中。你可以节省一些公羊。要利用此功能,请展开循环。同时使用float而不是double会增加这个机会
隐藏的依赖项:
现在这部分我个人觉得很棘手。有时您的编译器不是舒尔,它可以重用一些数据,因为它不知道这些数据没有更改。使用const
有助于编译器。但有时您需要一个restrict
来向编译器提供正确的提示。但我不太明白这一点,无法解释它
下面是我要尝试的:
const double ONETHIRD = 1.0 / 3.0;
assert(range % 4 == 0);
#pragma omp parallel for schedule(static, 1024)
for (int i = 0; i < range/4; i++) {
(*newData)[i*4+0] = ((*data)[i*4-1] + (*data)[i*4+0] + (*data)[i*4+1]) * ONETHIRD;
(*newData)[i*4+1] = ((*data)[i*4+0] + (*data)[i*4+1] + (*data)[i*4+2]) * ONETHIRD;
(*newData)[i*4+2] = ((*data)[i*4+1] + (*data)[i*4+2] + (*data)[i*4+3]) * ONETHIRD;
(*newData)[i*4+3] = ((*data)[i*4+2] + (*data)[i*4+3] + (*data)[i*4+4]) * ONETHIRD;
}
const-double-ONETHIRD=1.0/3.0;
断言(范围%4==0);
#计划的pragma omp并行(静态,1024)
对于(int i=0;i
然后进行基准测试。再基准一些,再基准一些。只有基准测试才能显示哪些技巧有用
PPS:还有一件事要考虑。如果你看到你的程序用硬键敲击内存带。你可以考虑改变算法。也许把两个步骤融合成一个步骤。喜欢从