优化C循环

时间:2011-02-21 08:45:16

标签: c optimization loops

我是多年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;
    }
    

4 个答案:

答案 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来加速算法。