我正在完成一项家庭作业,而且我的解决方案已经被困了几个小时。我们给出的问题是优化以下代码,以便它运行得更快,无论它变得多么混乱。我们应该使用诸如利用缓存块和循环展开之类的东西。
问题:
//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矩阵。
我也尝试过使用指针代替数组访问器,但我认为实际上不会以任何方式加速程序。
非常感谢任何帮助。
由于
答案 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.编译器必须假定src
和dst
可能是别名(指向相同或重叠的内存),这限制了其优化选项。如果你能以某种方式告诉编译器它们不能重叠,那可能会有很大的帮助。但是,我不知道该怎么做(也许使用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
可能会让事情变得更快一些。