缓存高效矩阵转置程序?

时间:2011-03-04 23:17:03

标签: algorithm caching matrix

因此转换矩阵的显而易见的方法是使用:

  for( int i = 0; i < n; i++ )

    for( int j = 0; j < n; j++ )

      destination[j+i*n] = source[i+j*n];

但我想要一些可以利用局部性和缓存阻塞的东西。我正在查找它并且找不到可以执行此操作的代码,但我被告知它应该是对原始的非常简单的修改。有任何想法吗?

编辑:我有一个2000x2000矩阵,我想知道如何使用两个for循环更改代码,基本上将矩阵拆分为单独转置的块,比如2x2块或40x40块,并查看哪个块大小最有效。

Edit2:矩阵以列主要顺序存储,即矩阵

a1 a2    
a3 a4

存储为a1 a3 a2 a4

6 个答案:

答案 0 :(得分:38)

你可能想要四个循环 - 两个循环遍历块,然后另外两个循环来执行单个块的转置副本。假设为了简单起见一个块大小来划分矩阵的大小,我认为这样的东西,虽然我想在信封的背面画一些图片来确定:

for (int i = 0; i < n; i += blocksize) {
    for (int j = 0; j < n; j += blocksize) {
        // transpose the block beginning at [i,j]
        for (int k = i; k < i + blocksize; ++k) {
            for (int l = j; l < j + blocksize; ++l) {
                dst[k + l*n] = src[l + k*n];
            }
        }
    }
}

一个重要的进一步见解是,实际上存在一个缓存无关的算法(参见http://en.wikipedia.org/wiki/Cache-oblivious_algorithm,它以此精确问题为例)。 “cache-oblivious”的非正式定义是,您无需尝试调整任何参数(在本例中为blocksize),以达到良好/最佳缓存性能。在这种情况下,解决方案是通过递归地将矩阵分成两半进行转置,并将半部转换到目的地的正确位置。

无论缓存大小是多少,这种递归都会利用它。与您的策略相比,我预计会有一些额外的管理开销,即使用性能实验实际上直接跳转到缓存真正起作用的递归点,并且不再进一步。另一方面,您的性能实验可能会为您提供适用于您的计算机但不适用于客户计算机的答案。

答案 1 :(得分:10)

昨天我遇到了同样的问题。 我最终得到了这个解决方案:

void transpose(double *dst, const double *src, size_t n, size_t p) noexcept {
    THROWS();
    size_t block = 32;
    for (size_t i = 0; i < n; i += block) {
        for(size_t j = 0; j < p; ++j) {
            for(size_t b = 0; b < block && i + b < n; ++b) {
                dst[j*n + i + b] = src[(i + b)*p + j];
            }
        }
    }
}

这比我机器上明显的解决方案快4倍。

此解决方案负责矩形矩阵,其尺寸不是块大小的倍数。

如果dst和src是相同的方阵,则应该使用原位函数:

void transpose(double*m,size_t n)noexcept{
    size_t block=0,size=8;
    for(block=0;block+size-1<n;block+=size){
        for(size_t i=block;i<block+size;++i){
            for(size_t j=i+1;j<block+size;++j){
                std::swap(m[i*n+j],m[j*n+i]);}}
        for(size_t i=block+size;i<n;++i){
            for(size_t j=block;j<block+size;++j){
                std::swap(m[i*n+j],m[j*n+i]);}}}
    for(size_t i=block;i<n;++i){
        for(size_t j=i+1;j<n;++j){
            std::swap(m[i*n+j],m[j*n+i]);}}}

我使用的是C ++ 11,但这可以很容易地用其他语言翻译。

答案 2 :(得分:7)

不是将矩阵转置到内存中,为什么不将转置操作折叠到你要对矩阵进行的下一个操作中呢?

答案 3 :(得分:5)

Steve Jessop提到了缓存无关的矩阵转置算法。 为了记录,我想分享缓存遗忘矩阵转置的可能实现。

public class Matrix {
    protected double data[];
    protected int rows, columns;

    public Matrix(int rows, int columns) {
        this.rows = rows;
        this.columns = columns;
        this.data = new double[rows * columns];
    }

    public Matrix transpose() {
        Matrix C = new Matrix(columns, rows);
        cachetranspose(0, rows, 0, columns, C);
        return C;
    }

    public void cachetranspose(int rb, int re, int cb, int ce, Matrix T) {
        int r = re - rb, c = ce - cb;
        if (r <= 16 && c <= 16) {
            for (int i = rb; i < re; i++) {
                for (int j = cb; j < ce; j++) {
                    T.data[j * rows + i] = data[i * columns + j];
                }
            }
        } else if (r >= c) {
            cachetranspose(rb, rb + (r / 2), cb, ce, T);
            cachetranspose(rb + (r / 2), re, cb, ce, T);
        } else {
            cachetranspose(rb, re, cb, cb + (c / 2), T);
            cachetranspose(rb, re, cb + (c / 2), ce, T);
        }
    }
}

可以找到有关缓存遗忘算法的更多详细信息here

答案 4 :(得分:2)

Matrix multiplication浮现在脑海中,但缓存问题更为明显,因为每个元素都被读取 N 次。

使用矩阵转置,您只需一次线性传递即可读取,并且无法对其进行优化。但是,您可以同时处理多个行,以便您编写多个列,从而填充完整的缓存行。你只需要三个循环。

或者以相反的方式进行,并在线性书写时读入列。

答案 5 :(得分:0)

对于一个大的矩阵,可能是一个大的稀疏矩阵,可能需要将它分解为更小的缓存友好块(Say,4x4子矩阵)。您还可以将子矩阵标记为标识,这将有助于您创建优化的代码路径。