实际上,它是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生成的汇编程序代码,但我完全迷失了。\
答案 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
,实际上会优于您的版本。
生成的程序集(使用Ep
和Si
的某些任意值)如下所示:
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 +%样本上的例程,加上其中负责时间的行,加上其调用者负责时间,他们的来电,等等。堆栈中的所有代码行共同负责时间。
当没有时间占用大部分时间时,优化性不是,当没有任何你可以修复需要花费大量时间时。