有没有更好的方法来优化lennard jones潜在功能?

时间:2011-02-07 15:10:07

标签: c performance

实际上,它是Lennard Jones潜力的衍生物。原因是我正在编写分子动力学程序,并且至少有80%的时间用在以下函数中,即使使用最具侵略性的编译器选项(gcc * * -O3)。 / p>

double ljd(double r) /* Derivative of Lennard Jones Potential for Argon with 
                        respect to distance (r) */ 
{  
    double temp;  
    temp = Si/r;  
    temp = temp*temp;
    temp = temp*temp*temp;  
    return ( (24*Ep/r)*(temp-(2 * pow(temp,2))) );  
}  

此代码来自文件“functs.h”,我将其导入到我的主文件中。我认为以这种方式使用临时变量会使函数更快,但我担心创建它们太浪费了。我应该使用静电吗?此外,代码是使用openmp并行编写的,所以我不能将temp声明为全局变量吗?

定义变量Ep和Si(使用#define)。我只使用C约1个月。我试着看看gcc生成的汇编程序代码,但我完全迷失了。\

6 个答案:

答案 0 :(得分:7)

我将摆脱对pow()的号召开始:

double ljd(double r) /* Derivative of Lennard Jones Potential for Argon with 
                        respect to distance (r) */ 
{  
    double temp;  
    temp = Si / r;  
    temp = temp * temp;
    temp = temp * temp * temp;  
    return ( (24.0 * Ep / r) * (temp - (2.0 * temp * temp)) );  
}  

答案 1 :(得分:3)

在我的架构上(英特尔Centrino Duo,Windows XP上的MinGW-gcc 4.5.2),使用pow()的非优化代码

static inline double ljd(double r)
{
    return 24 * Ep / Si * (pow(Si / r, 7) - 2 * pow(Si / r, 13));
}
如果提供-ffast-math

实际上会优于您的版本。

生成的程序集(使用EpSi的某些任意值)如下所示:

fldl    LC0
fdivl   8(%ebp)
fld     %st(0)
fmul    %st(1), %st
fmul    %st, %st(1)
fld     %st(0)
fmul    %st(1), %st
fmul    %st(2), %st
fxch    %st(1)
fmul    %st(2), %st
fmul    %st(0), %st
fmulp   %st, %st(2)
fxch    %st(1)
fadd    %st(0), %st
fsubrp  %st, %st(1)
fmull   LC1

答案 2 :(得分:1)

好吧,正如我之前所说,编译器因为许多原因而不得不优化浮点代码。所以,这是一个更快的英特尔汇编版本(使用DevStudio 2005编译):

const double Si6 = /*whatever pow(Si,6) is*/;
const double Si_value = /*whatever Si is*/; /* need _value as Si is a register name! */
const double Ep24 = /*whatever 24.Ep is*/;

double ljd (double r)
{
  double result;
  __asm
  {
    fld qword ptr [r]
    fld st(0)
    fmul st(0),st(0)
    fld st(0)
    fmul st(0),st(0)
    fmulp st(1),st(0)
    fld qword ptr [Si6]
    fdivrp st(1),st(0)
    fld st(0)
    fld1
    fsub st(0),st(1)
    fsubrp st(1),st(0)
    fmulp st(1),st(0)
    fld qword ptr [Ep24]
    fmulp st(1),st(0)
    fdivrp st(1),st(0)
    fstp qword ptr [result]
  }

  return result;
}

此版本会对发布的版本产生稍微不同的结果。编译器可能会在原始代码中将中间结果写入RAM。这将失去精度,因为(英特尔)FPU内部工作在80位,而双重类型只有64位。上述汇编程序不会在中间结果中失去精度,它全部在80位完成。只有最终结果四舍五入为64位。

答案 3 :(得分:1)

啊,这给我带来了一些回忆......几年前我和Lennard Jones一起做过MD。

在我的场景中(不是大型系统),用另一个答案建议,用几次乘法替换pow()就足够了。我还限制了邻居的范围,有效地截断了大约r ~ 3.5的潜力,并在之后应用了一些标准的热力学校正。

但如果这一切对你来说还不够,我建议预先计算函数的间距为r,并简单插值(线性或二次,我会说)。

答案 4 :(得分:1)

您的应用程序是否以这样的方式构建,即您可以有效地向量化该函数,并行计算多个独立值?这将允许您使用硬件向量单元,例如SSE。

看起来您最好保留1/r值,而不是r本身。


这是一个明确使用SSE2指令来实现该功能的示例。 ljd()一次计算两个值。

static __m128d ljd(__m128d r)
{
    static const __m128d two = { 2.0, 2.0 };
    static const __m128d si = { Si, Si };
    static const __m128d ep24 = { 24 * Ep, 24 * Ep };

    __m128d temp2, temp3;
    __m128d temp = _mm_div_pd(si, r);
    __m128d ep24_r = _mm_div_pd(ep24, r);

    temp = _mm_mul_pd(temp, temp);
    temp2 = _mm_mul_pd(temp, temp);
    temp2 = _mm_mul_pd(temp2, temp);
    temp3 = _mm_mul_pd(temp2, temp2);
    temp3 = _mm_mul_pd(temp3, two);

    return _mm_mul_pd(ep24_r, _mm_sub_pd(temp2, temp3));
}

/* Requires `out` and `in` to be 16-byte aligned */
void ljd_array(double out[], const double in[], int n)
{
    int i;

    for (i = 0; i < n; i += 2)
    {
        _mm_store_pd(out + i, ljd(_mm_load_pd(in + i)));
    }
}

但是,重要的是要注意,只要您正在定位正确的架构并启用了优化,最新版本的GCC通常能够自动向这样的函数进行矢量化。如果您的目标是32位x86,请尝试使用-msse2 -O3进行编译,并调整输入和输出数组以16字节对齐的方式。

静态和自动数组的对齐可以使用类型属性__attribute__ ((aligned (16)))在gcc下实现,对于使用posix_memalign()函数的动态数组也可以实现。

答案 5 :(得分:1)

局部变量很好。它不需要任何费用。别管它了。

正如其他人所说,摆脱pow电话。它不能比简单地平方数字更快,而且可能是 lot 更慢。

那就是说,仅仅因为功能有效80 +%的时间并不意味着它是一个问题。它只是意味着如果你可以优化某些东西,它可以在那里,也可以在它调用的东西中(如pow)或者用它来调用

如果您尝试使用random pausing这是一种堆栈采样方法,您将看到80 +%样本上的例程,加上其中负责时间的行,加上其调用者负责时间,他们的来电,等等。堆栈中的所有代码行共同负责时间。

当没有时间占用大部分时间时,优化性不是,当没有任何你可以修复需要花费大量时间时。

相关问题