优化嵌套循环

时间:2012-05-23 20:36:24

标签: c optimization openmp

我们有一个任务,我们给了一个非常低效的程序,我们必须优化代码,使其运行更快。除了这两个之外,我的大部分时间都非常快,这让我非常烦恼,因为它们非常简单。一个基本上将二维数组中的所有值设置为相同的值,并且基本上交换两个2-D数组的值。这就是问题所在。他们占用的时间最多,但因为它们非常简单,我无法弄清楚如何在不破坏功能的情况下减少它们。我知道有可能让他们跑得更快,因为班上的其他学生已经变得荒谬可笑。这两个问题是:

fSetArray(int rows, int cols, float val)
{
    int i, j;
    F2D *out;
    out = fMallocHandle(rows, cols);

    for(i=0; i<rows; i++)
    for(j=0; j<cols; j++)
        subsref(out,i,j) = val;

    return out;

}

唯一重要的部分是那里的两个循环。基本上,我们有一个具有一定宽度(行)和一定高度(cols)的二维数组,我们将所有值设置为val。但是我无法看到消除其中一个循环(这将是加速循环的最佳方法)。除非我遗漏了一些明显的东西。如果cols和rows是相同的数字,这将更容易。

另一个功能是:

fDeepCopy(F2D* in)
{
    int i, j;
    F2D* out;
    int rows, cols;

    rows = in->height;
    cols = in->width;

    out = fMallocHandle(rows, cols);

    for(i=0; i<rows; i++)
    for(j=0; j<cols; j++)
        subsref(out,i,j) = subsref(in,i,j);

    return out;
}

out数组总是大于in数组,所以我们不必担心溢出等。这个函数基本上只是交换了两个数组的值,但是再一次,因为它很简单,我不能完全减少它。

在有人说并行化之前,由于样本大小和服务器,创建线程所需的开销实际上会降低程序的速度。我试过了。因为这两个函数太短,所以创建线程不值得在一个并行之后杀死它们。

但是作为参考,从技术上讲,这是一个OpenMP项目,我们应该使用并行化,但对于这两个函数,这样做实际上会减慢整个程序。我用一个并行的for循环来计时它。

编辑:感谢所有给我想法的人!我现在已经开始运行并全速运行了!

2 个答案:

答案 0 :(得分:1)

一种优化是循环展开。有时管道需要停止,因为在获取索引,更新索引以及将其存储回内存方面存在大量活动。这可能是您的多线程实现不能很好的主要原因,所有线程可能都在争夺对索引的访问权。

如果要重新尝试多线程实现,请让每个线程根据线程数知道它的“偏移量”,并让每个线程处理通过模数除法发现的不同余数

thread 0 works on i*rows+j % (thread count) = 0
thread 1 works on i*rows+j % (thread count) = 1
(and so on)

如果您不想重新尝试多线程实现,仍有一些技术可以提高性能。有时,即使单个线程也可能不必要地在索引变量上停滞不前(因为它会导致处理器管道的无效使用)。要尝试修复此类问题,请将索引增加一个特定的数字,并在循环中调用该方法的次数。

fDeepCopy(F2D* in)
{
    int i, j;
    F2D* out;
    int rows, cols;

    rows = in->height;
    cols = in->width;

    out = fMallocHandle(rows, cols);

    for(i=0; i<rows; i++) {
      // rewrite to ensure we don't walk off "4 long" pads
      int j = 0;
      int pads = (cols / 4)*4;
      for(; j < pads; j = j + 4) {
        subsref(out,i,j) = subsref(in,i,j);
        subsref(out,i,j+1) = subsref(in,i,j+1);
        subsref(out,i,j+2) = subsref(in,i,j+2);
        subsref(out,i,j+3) = subsref(in,i,j+3);
      }
      // process the remainders
      for(; j < pads; j++) {
        subsref(out,i,j) = subsref(in,i,j);
      }
    }
    return out;
}

现在,CPU设计人员正在变得越来越好,因此严重实际上是对您的运行进行概要分析以确定CPU是否已经为您进行了这样的优化,或者这样的技术实际上可能会降低您的代码速度。

最后,您还可以leverage SSE extensions,它在适当的条件下可以对存储在MMX寄存器中的多个值执行相同的操作。例如,您可以使用汇编内联将两组四个32位单精度浮点数打包到MMX寄存器中,并批量执行添加,一次执行四个浮点。

所以,看起来像

vec_res.x = v1.x + v2.x;
vec_res.y = v1.y + v2.y;
vec_res.z = v1.z + v2.z;
vec_res.w = v1.w + v2.w;

需要8个内存查找,4个添加和4个存储(16个未优化的操作)可能会被替换为

movaps xmm0, [v1]          ;xmm0 = v1.w | v1.z | v1.y | v1.x 
addps xmm0, [v2]           ;xmm0 = v1.w+v2.w | v1.z+v2.z | v1.y+v2.y | v1.x+v2.x               
movaps [vec_res], xmm0

只需要三次操作,未经优化。

此外,关键是分析,因此您可以检测瓶颈,然后调整代码,使其处于最低可接受的性能范围内。

答案 1 :(得分:0)

memset应对第一个功能有所帮助。