我同意为了更好的缓存而进行转置 (但请看我的评论),还有更多工作要做,所以让我们看看我们可以用完整函数做些什么...
参考原始函数(已经进行了一些整理):
void MultiDiagonalSymmetricMatrix::CholeskyBackSolve(float *x, float *b){
float *y = new float[n];
float **d = IncompleteCholeskyFactorization->Diagonals;
unsigned int *s = IncompleteCholeskyFactorization->StartRows;
unsigned int M = IncompleteCholeskyFactorization->m;
unsigned int N = IncompleteCholeskyFactorization->n;
unsigned int i, j;
for(j = 0; j != N; j++){
float sub = 0;
for(i = 1; i != M; i++){
int c = (int)j - (int)s[i];
if(c < 0) break;
if(c==j) {
sub += d[i][c]*b[c];
} else {
sub += d[i][c]*y[c];
}
}
y[j] = b[j] - sub;
}
#pragma omp parallel for
for(j = 0; j < N; j++){
x[j] = y[j]/d[0][j];
}
while(j-- != 0){
float sub = 0;
for(i = 1; i != M; i++){
if(j + s[i] >= N) break;
sub += d[i][j]*x[j + s[i]];
}
x[j] -= sub;
}
delete[] y;
}
由于关于并行分割可以提高速度的评论(尽管只有O(N)),我假设函数本身被频繁调用。那么为什么要分配内存呢?只需将x标记为__restrict__,并在所有地方将y更改为x(__restrict__是来自C99的GCC扩展名。您可能希望为其使用define。也许该库已经有了一个)。
同样地,虽然我猜您无法更改签名,但可以使函数仅接受单个参数并进行修改。当设置了x或y时,b从未被使用。这也意味着您可以在第一个循环中摆脱运行约N*M次的分支。如果必须使用两个参数,请在开始时使用memcpy。
d为什么是指针数组?必须吗?这似乎太深入原始代码,所以我不会去动它,但是如果有任何扁平化存储数组的可能性,即使您无法转置它,它也将提高速度(乘法,加法,解除引用比解除引用,加法,解除引用更快)。
因此,新代码:
void MultiDiagonalSymmetricMatrix::CholeskyBackSolve(float *__restrict__ x){
const float *const __restrict__ *const __restrict__ d = IncompleteCholeskyFactorization->Diagonals;
const unsigned int *const __restrict__ s = IncompleteCholeskyFactorization->StartRows;
const unsigned int M = IncompleteCholeskyFactorization->m;
const unsigned int N = IncompleteCholeskyFactorization->n;
unsigned int i;
unsigned int j;
for(j = 0; j < N; j++){
float sub = 0;
for(i = 1; i < M && j >= s[i]; i++){
const unsigned int c = j - s[i];
sub += d[i][c]*x[c];
}
x[j] -= sub;
}
#pragma omp parallel for
for(j = 0; j < N; j++){
x[j] /= d[0][j];
}
for( j = N; (j --) > 0; ){
float sub = 0;
for(i = 1; i < M && j + s[i] < N; i++){
sub += d[i][j]*x[j + s[i]];
}
x[j] -= sub;
}
}
现在看起来更整洁了,如果没有其他东西,内存分配的缺乏和减少的分支是一种提升。如果您可以将s
更改为在末尾包括额外的UINT_MAX
值,则可以删除更多的分支(包括i<M
检查,这些检查再次运行~ N * M次)。
现在我们不能再并行进行任何循环,也不能组合循环。如其他答案所建议的那样,重新排列d
将会有所提高。但是……重新排列d
所需的工作与执行循环的工作具有完全相同的缓存问题。而且还需要分配内存。不好。进一步优化的唯一选择是:更改IncompleteCholeskyFactorization->Diagonals
本身的结构,这可能需要进行大量更改,或者找到一个使用此顺序数据效果更好的不同算法。
如果您想进一步进行优化,则需要影响代码的相当大部分(除非Diagonals
作为指针数组存在充分理由,否则看起来需要进行重构)。
__restrict__
?i
和j
实际上有多大?“这个循环是递归的……”--请提供更多上下文信息。 - Brian Cain