14

我是名为 vampire ( http://github.com/richard-evans/vampire )的开源科学代码的作者,计算密集型意味着代码性能的任何改进都可以显着增加可以完成的研究量。这段代码的典型运行时间可能是数百个核心小时,所以我一直在寻找提高代码性能关键部分的方法。但是,我有点坚持以下看起来相对无害的代码,它占运行时的 40% 左右:

for (int atom = start_index; atom < end_index; atom++){
    register double Hx = 0.0;
    register double Hy = 0.0;
    register double Hz = 0.0;
    const int start = atoms::neighbour_list_start_index[atom];
    const int end = atoms::neighbour_list_end_index[atom] + 1;
    for (int nn = start; nn < end; nn++){
        const int natom = atoms::neighbour_list_array[nn];
        const double Jij = atoms::i_exchange_list[atoms::neighbour_interaction_type_array[nn]].Jij;
        Hx -= Jij * atoms::x_spin_array[natom];
        Hy -= Jij * atoms::y_spin_array[natom];
        Hz -= Jij * atoms::z_spin_array[natom];
    }
    atoms::x_total_spin_field_array[atom] += Hx;
    atoms::y_total_spin_field_array[atom] += Hy;
    atoms::z_total_spin_field_array[atom] += Hz;
}

此代码的函数和变量的高级概述如下: 有一个物理向量的 1D 数组(为每个分量 x、y、z 拆分为三个 1D 数组,用于内存缓存atoms::x_spin_array等),称为 'spin '。这些自旋中的每一个都与其他一些自旋交互,并且所有交互都存储为一维邻居列表 ( atoms::neighbour_list_array)。每个原子的相关相互作用列表由listarray两个单独数组中邻居的开始和结束索引确定。在计算结束时,每个原子自旋都有一个有效场,它是相互作用的矢量和。

考虑到少量的代码和它占据的运行时间的相当大一部分,我已经做到了最好,但我觉得必须有一种方法可以进一步优化这一点,但作为物理学家而不是计算机科学家,也许我错过了什么?

4

3 回答 3

6

您在连续数据上得到了一个恒定的乘法、减法和加法流。这似乎是 SSE 的理想用途。如果它的内存带宽有限,那么 OpenCL/CUDA 代替。

如果您不熟悉所有低级指令,请尝试使用此库。

该内部循环可能会被显着重组,可能会导致加速。

于 2013-07-23T09:50:50.447 回答
2

如果x, y,z组件确实是链表,做x[i],y[i]并且z[i]会导致链表被多次遍历,给出(n^2)/2迭代。使用向量将使这成为一项O(1)操作。

您提到三个坐标是出于内存缓存目的而拆分的,但这会影响一级和二级缓存的位置,因为您正在访问内存中的 3 个不同区域。链表也会影响您的缓存位置。

使用类似的东西:

struct vector3d {
    double x;
    double y;
    double z;
};

std::vector<vector3d> spin;
std::vector<vector3d> total_spin;

这应该会改善缓存局部性,因为 x、y 和 z 值在内存中是相邻的,并且自旋占用内存的线性块。

于 2013-07-23T10:34:21.153 回答
0

我觉得以下建议可以帮助您稍微优化代码(如果不是完全的话):

  1. 尽可能对赋值使用初始化
  2. 更喜欢预增量而不是帖子以获得更好的速度。(相信我,它确实会做出改变)

除此之外,我认为代码很好。每个 DS 都有一些优点和缺点..你必须忍受它。

快乐编码!

于 2013-07-23T09:04:28.180 回答