优化数组转置功能

时间:2012-05-30 05:37:50

标签: c caching optimization loops matrix

我正在完成一项家庭作业,而且我的解决方案已经被困了几个小时。我们给出的问题是优化以下代码,以便它运行得更快,无论它变得多么混乱。我们应该使用诸如利用缓存块和循环展开之类的东西。

问题:

//transpose a dim x dim matrix into dist by swapping all i,j with j,i
void transpose(int *dst, int *src, int dim) {
    int i, j;

    for(i = 0; i < dim; i++) {
        for(j = 0; j < dim; j++) {
                dst[j*dim + i] = src[i*dim + j];
        }
    }
}

到目前为止我所拥有的:

//attempt 1
void transpose(int *dst, int *src, int dim) {
    int i, j, id, jd;

    id = 0;
    for(i = 0; i < dim; i++, id+=dim) {
        jd = 0;
        for(j = 0; j < dim; j++, jd+=dim) {
                dst[jd + i] = src[id + j];
        }
    }
}

//attempt 2
void transpose(int *dst, int *src, int dim) {
    int i, j, id;
    int *pd, *ps;
    id = 0;
    for(i = 0; i < dim; i++, id+=dim) {
        pd = dst + i;
        ps = src + id;
        for(j = 0; j < dim; j++) {
                *pd = *ps++;
                pd += dim;
        }
    }
}

有些想法,如果我错了,请纠正我:

我已经考虑过循环展开但我不认为这会有所帮助,因为我们不知道NxN矩阵是否具有主要维度。如果我检查了它,它将包括过多的计算,这只会减慢功能。

缓存块不是很有用,因为无论如何,我们将线性访问一个数组(1,2,3,4),而另一个我们将在N的跳转中访问。虽然我们可以得到函数滥用缓存并更快地访问src块,它仍然需要很长时间才能将它们放入dst矩阵。

我也尝试过使用指针代替数组访问器,但我认为实际上不会以任何方式加速程序。

非常感谢任何帮助。

由于

5 个答案:

答案 0 :(得分:7)

缓存阻止可能很有用。举个例子,假设我们的缓存行大小为64字节(这是x86目前使用的)。因此,对于足够大的矩阵,使其大于缓存大小,那么如果我们转置16x16块(因为sizeof(int)== 4,因此16个整数适合缓存行,假设矩阵在缓存行边界上对齐) )我们需要从内存中加载32(来自源矩阵的16个,来自目标矩阵的16个)来自内存的缓存行并存储另外16行(即使存储不是连续的)。相反,如果没有高速缓存阻塞转置等效的16 * 16元素,则需要我们从源矩阵加载16个高速缓存行,但要加载16 * 16 = 256个高速缓存行,然后存储到目标矩阵。

答案 1 :(得分:3)

展开对于大型矩阵非常有用 如果矩阵大小不是您展开次数的倍数,则需要一些代码来处理多余的元素。但这将超出最关键的循环,因此对于大型矩阵来说,它是值得的。

关于访问的方向 - 线性读取和写入N的跳跃可能更好,而不是相反。这是因为读操作会阻塞CPU,而写操作则不会(最多限制)。

其他建议:
你可以使用并行化吗? OpenMP可以提供帮助(尽管如果您希望提供单CPU性能,那就不行了。) 2.拆卸功能并读取,重点放在最内圈。你可能会发现在C代码中你不会注意到的事情 3.使用递减计数器(停止在0)可能比增加计数器更有效 4.编译器必须假定srcdst可能是别名(指向相同或重叠的内存),这限制了其优化选项。如果你能以某种方式告诉编译器它们不能重叠,那可能会有很大的帮助。但是,我不知道该怎么做(也许使用restrict限定词)。

答案 2 :(得分:1)

凌乱不是问题,所以:我会在每个矩阵中添加transposed标志。该标志指示矩阵的存储数据阵列是以正常顺序还是以转置顺序解释。

除了每个矩阵参数之外,所有矩阵运算都应该接收这些新标志。在每个操作内部实现所有可能的标志组合的代码。也许宏可以在这里节省多余的写作。

在这个新实现中,矩阵转置只是切换标志:转置操作所需的空间和时间是不变的。

答案 3 :(得分:0)

只是想知道如何实现展开:

void transpose(int *dst, int *src, int dim) {
    int i, j;
    const int dim1 = (dim / 4) * 4;

    for(i = 0; i < dim; i++) {
        for(j = 0; j < dim1; j+=4) {
                dst[j*dim + i]     = src[i*dim + j];
                dst[(j+1)*dim + i] = src[i*dim + (j+1)];
                dst[(j+2)*dim + i] = src[i*dim + (j+2)];
                dst[(j+3)*dim + i] = src[i*dim + (j+3)];
        }
        for( ; j < dim; j++) {
                dst[j*dim + i] = src[i*dim + j];
        }
        __builtin_prefetch (&src[(i+1)*dim], 0, 1);
    }
}

对于cource,您应该从内循环中删除计数(如i*dim),就像您在尝试中所做的那样。

缓存预取可用于源矩阵。

答案 4 :(得分:-1)

你可能知道这个但是register int(你告诉编译器将它放入寄存器是明智的)。制作int unsigned可能会让事情变得更快一些。