我是多年Matlab数学编程的新手。我已经开发了一个解决大型微分方程系统的程序,但我很确定我做了一些愚蠢的事情,因为在对代码进行分析后,我惊讶地看到三个循环占据了大约90%的计算量时间,尽管他们正在执行该计划中最微不足道的步骤。
基于这些昂贵的循环,我的问题分为三部分:
将数组初始化为零。当J被声明为双数组时,数组的值是否初始化为零?如果没有,是否有一种快速的方法将所有元素设置为零?
void spam(){
double J[151][151];
/* Other relevant variables declared */
calcJac(data,J,y);
/* Use J */
}
static void calcJac(UserData data, double J[151][151],N_Vector y)
{
/* The first expensive loop */
int iter, jter;
for (iter=0; iter<151; iter++) {
for (jter = 0; jter<151; jter++) {
J[iter][jter] = 0;
}
}
/* More code to populate J from data and y that runs very quickly */
}
在求解过程中,我需要求解由P = I - gamma * J定义的矩阵方程。 P的构造比解决它定义的方程组花费的时间更长,所以我正在做的事情可能是错误的。在下面相对较慢的循环中,访问一个矩阵,该矩阵包含在慢速组件的结构“数据”中,或者它是关于循环的其他内容吗?
for (iter = 1; iter<151; iter++) {
for(jter = 1; jter<151; jter++){
P[iter-1][jter-1] = - gamma*(data->J[iter][jter]);
}
}
矩阵乘法是否有最佳实践?在下面的循环中,Ith(v,iter)是一个宏,用于获取在N_Vector结构'v'(Sundials求解器使用的数据类型)中保存的向量的第component分量。特别是,是否有一种最佳方法可以获得v与J行之间的点积?
Jv_scratch = 0;
int iter, jter;
for (iter=1; iter<151; iter++) {
for (jter=1; jter<151; jter++) {
Jv_scratch += J[iter][jter]*Ith(v,jter);
}
Ith(Jv,iter) = Jv_scratch;
Jv_scratch = 0;
}
答案 0 :(得分:4)
1)不,他们不是你可以如下设置数组:
memset( J, 0, sizeof( double ) * 151 * 151 );
或者你可以使用数组初始化器:
double J[151][151] = { 0.0 };
2)你使用相当复杂的计算来计算P的位置和J的位置。
你可能会获得更好的表现。通过逐步指出:
for (iter = 1; iter<151; iter++)
{
double* pP = (P - 1) + (151 * iter);
double* pJ = data->J + (151 * iter);
for(jter = 1; jter<151; jter++, pP++, pJ++ )
{
*pP = - gamma * *pJ;
}
}
这样就可以在循环之外移动各种数组索引计算。
3)最佳做法是尝试尽可能多地从循环中移出计算。就像我在上面的循环中所做的那样。
答案 1 :(得分:3)
首先,我建议你将问题分成三个单独的问题。很难回答这三个问题;例如,我对数值分析的工作量不大,所以我只回答第一个问题。
首先,堆栈上的变量不为您初始化。但是有更快的方法来初始化它们。在你的情况下,我建议使用memset:
static void calcJac(UserData data, double J[151][151],N_Vector y)
{
memset((void*)J, 0, sizeof(double) * 151 * 151);
/* More code to populate J from data and y that runs very quickly */
}
memset
是一个快速库例程,用特定的字节模式填充内存区域。恰好将double
的所有字节设置为零会将double
设置为零,因此利用库的快速例程(可能会用汇编语言编写,以利用SSE之类的东西) )。
答案 2 :(得分:1)
其他人已经回答了你的一些问题。关于矩阵乘法的问题;除非你对缓存架构等了解很多,否则很难为此编写一个快速算法(访问数组元素导致数千个缓存未命中的顺序会导致缓慢)。
如果您想了解有关“矩阵乘法”,“缓存”,“阻止”等字词,可以尝试谷歌搜索快速库中使用的技术。但我的建议是,如果性能很关键,只需使用预先存在的数学库。
答案 3 :(得分:0)
将数组初始化为零。 当J被宣布为双倍时 array是数组的值 初始化为零?如果没有,是吗? 一种快速设置所有元素的方法 为零?
这取决于数组的分配位置。如果它在文件范围内声明,或者作为静态声明,则C标准保证所有元素都设置为零。如果在初始化时将第一个元素设置为值,则保证相同,即:
double J[151][151] = {0}; /* set first element to zero */
通过将第一个元素设置为某个值,C标准保证数组中的所有其他元素都设置为零,就好像数组是静态分配的一样。
实际上对于这个特定情况,我非常怀疑无论你使用哪个系统,在堆栈上分配151 * 151 * sizeof(双)字节都是明智的。您可能必须动态分配它,然后上述情况都不重要。然后必须使用memset()将所有字节设置为零。
在 下面比较慢的循环,是 访问包含的矩阵 在结构'数据'中慢 组件还是其他东西 关于循环?
您应该确保从中调用的函数是内联的。否则,您无法做出更多优化循环的工作:最佳的是高度依赖系统的(即如何构建物理高速缓存存储器)。最好将这样的优化留给编译器。
你当然可以通过手动优化的东西来混淆代码,例如向下计数而不是向上计数,或者使用++ i而不是i ++等等。但是编译器真的应该能够为你处理这些事情。
至于矩阵添加,我不知道数学上最有效的方法,但我怀疑它与代码的效率有很小的关联。这里的大时代小偷是双人型。除非你真的需要高精度,否则我会考虑使用float或int来加速算法。