gpt4 book ai didi

c++ - OpenMP和OOP(分子动力学仿真)

转载 作者:行者123 更新时间:2023-12-03 20:18:12 25 4
gpt4 key购买 nike

我正在进行分子动力学仿真,并且为并行实现它已经进行了相当长的时间,尽管我成功加载了4线程处理器,但是并行计算的时间却大于并行计算的时间。串行模式。

在研究每个线程在哪个时间点开始和结束其循环迭代时,我注意到了一种模式:好像不同的线程在互相等待。
那时,我将注意力转向了程序的结构。我有一个类,该类的实例代表我的粒子系统,其中包含有关粒子的所有信息以及一些使用此信息的函数。我还有一个代表我的原子间势的类实例,其中包含势函数的参数以及一些函数(这些函数之一计算两个给定粒子之间的力)。

因此,在我的程序中,存在两个不同类的实例,并且它们彼此交互:一个类的某些功能引用了另一个类的实例。
我尝试并行实现的块如下所示:

      void Run_simulation(Class_system &system, Class_potential &potential, some other arguments){
#pragma omp parallel for
for(…)
}

for(...)是实际的计算,使用 system类的 Class_system实例中的数据和 potential类的 Class_potential实例中的一些函数。

我是不是因为这种结构而引起了麻烦?

您能建议我在这种情况下必须做什么吗?我是否必须以完全不同的方式重写程序?我是否应该使用其他工具并行执行程序?

最佳答案

如果没有您的模拟类型的更多详细信息,我只能推测,所以这是我的推测。

您是否研究了负载平衡问题?我猜想循环将粒子分布在线程之间,但是如果您有某种有限的范围潜力,那么根据空间密度的不同,模拟体积在不同区域的计算时间可能会有所不同。这是分子动力学中非常普遍的问题,很难在分布式内存(大多数情况下为MPI)代码中正确解决。幸运的是,使用OpenMP,您可以直接访问每个计算元素上的所有粒子,因此负载平衡更容易实现。可以说,它不仅更简单,而且是内置的-只需使用for子句更改schedule(dynamic,chunk)指令的调度即可,其中chunk是一个很小的数字,其最佳值可能因仿真而异。您可以将chunk作为程序输入数据的一部分,或者改为编写schedule(runtime),然后通过将OMP_SCHEDULE环境变量设置为"static""dynamic,1""dynamic,10""guided"等值来使用不同的调度类。

性能降低的另一个可能原因是错误共享和真实共享。当您的数据结构不适合并发修改时,就会发生错误共享。例如,如果您为每个粒子保留3D位置和速度信息(假设您使用速度Verlet积分器),则在给定IEEE 754 double 的情况下,每个坐标/速度三元组占用24个字节。这意味着一个64字节的高速缓存行可容纳2个完整的三元组,而另一个则占2/3。这样的结果是,无论您如何在线程之间分配粒子,总是至少会有两个线程共享一个缓存行。假设这些线程在不同的物理内核上运行。如果一个线程写入其高速缓存行的副本(例如,它更新粒子的位置),则将涉及高速缓存一致性协议(protocol),它将使另一线程中的高速缓存行无效,然后必须从中重新读取它即使是主内存中的缓存也较慢。当第二个线程更新其粒子时,这将使第一个内核中的缓存行无效。解决此问题的方法是使用适当的填充和适当的块大小选择,以便没有两个线程共享一个高速缓存行。例如,如果添加一个表面的第4维(可以使用它在位置 vector 的第4个元素中存储粒子的势能,在速度 vector 的第4个元素中存储动能)那么每个位置/速度四元组将占用32个字节,而恰好两个粒子的信息将放入一条高速缓存行中。如果然后在每个线程中分配偶数个粒子,则会自动消除可能的错误共享。

当线程同时访问同一数据结构,并且结构的各个部分之间存在重叠(由不同的线程修改)时,就会发生真正的共享。在分子动力学模拟中,这种情况经常发生,因为我们想利用牛顿第三定律,以便在处理成对的相互作用势时将计算时间减少为两个。当一个线程计算作用在粒子i上的力时,在枚举其邻居j时,计算j施加在i上的力会自动为您提供i施加在j上的力,这样就可以将作用力添加到j的总力上。但是j可能属于另一个线程,该线程可能正在同时对其进行修改,因此两个更新都必须使用原子操作(两者都是,如果另一个线程碰巧与自己的多个粒子中的一个相邻,则另一个线程可能会更新i)。 x86上的原子更新是通过锁定指令实现的。这并没有经常出现的那么慢,但是仍然比常规更新慢。它还包括与错误共享相同的缓存行无效效果。为了解决这个问题,以增加内存使用为代价,可以使用局部数组来存储部分力的贡献,然后最终进行减少。减少本身必须与锁定指令以串行方式或并行方式执行,因此可能会发现,使用这种方法不仅没有 yield ,而且速度可能会更慢。适当的颗粒分类和在处理元件之间的巧妙分配,以最小化界面区域,可以用来解决这个问题。

我想谈谈的另一件事是内存带宽。根据您的算法,在循环的每次迭代中获取的数据元素的数量与浮点操作的数量之间存在一定的比率。每个处理器只有有限的带宽可用于内存提取,如果碰巧您的数据不太适合CPU缓存,则可能发生内存总线无法传递足够的数据来馈送如此多的线程在单个内存中执行的情况。插座。 Core i3-2370M的L3高速缓存只有3 MiB,因此,如果您明确保留每个粒子的位置,速度和力,则只能在L3高速缓存中存储大约43000个粒子,在L2高速缓存中只能存储大约3600个粒子(或大约1800个)每个超线程的粒子数)。

最后一个是超线程。正如高性能标记已经指出的那样,超线程共享大量的核心机制。例如,只有一个AVX vector FPU引擎在两个超线程之间共享。如果未对代码进行矢量化处理,则会损失处理器中大量可用的计算能力。如果您的代码是矢量化的,则这两个超线程将在争夺对AVX引擎的控制权时互相介入。仅当超线程能够通过将计算(在一个超线程中)与内存负载(在另一个超线程中)覆盖来隐藏内存延迟时,超线程才有用。借助密集的数字代码在执行内存加载/存储之前执行许多寄存器操作,超线程丝毫没有任何好处,您最好以一半的线程数量运行并将其显式绑定(bind)到不同的内核,以防止OS调度程序运行它们作为超线程。在这方面,Windows上的调度程序特别笨拙,请参阅here作为示例。英特尔的OpenMP实现支持通过环境变量控制的各种绑定(bind)策略。 GNU的OpenMP实现也是如此。我不知道在Microsoft的OpenMP实现中有什么方法可以控制线程绑定(bind)(也称为亲和力掩码)。

关于c++ - OpenMP和OOP(分子动力学仿真),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13958858/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com