对于矩阵乘法而言,一维数组显然比二维数组好得多(下文的分析也能看出这一点)。但是如果用矩阵类实现其他功能的话,可能其他功能用二维数组更方便一些。所以下面对于这两种实现方式都加以分析。
造成矩阵乘法慢的原因,除了算法上的 O(n^3) 以外,还有内存访问不连续。这会导致cache命中率不高。所以为了加速,就要尽可能使内存访问连续,即不要跳来跳去。我们定义一个概念:跳跃数,来衡量访问的不连续程度。
对于最普通的实现方式(顺序: ijk ),它是依次计算 C 中的每个元素。当计算 C 中任一个元素时,需要将 A 对应的行与 B 对应的列依次相乘相加。之前已假设过,矩阵是按行存储的,所以在 A 相应行中不断向右移动时,内存访问是连续的。但 B 相应列不断向下移动时,内存访问是不连续的。计算完 C 的一个元素时, B 相应列中已经间断地访问了 n 次,而 A 只间断 1 次(这一次就是算完后跳转回本行的开头),故总共是 n+1 次。这样计算完 C 中所有 n^2 个元素,跳转了 n^3+n^2 次。但刚才没有计数 C 的跳转次数,加上以后是 n^3+n^2+n 。(注意,在计算完 C 中每行的最后一个元素时, A 是从相应行末尾转到下一行开头。如果使用一维数组实现的话,这是连续地访问,要减掉这 n 次。同时, C 没有跳转次数了,还要减掉 n 次。因此对于一维数组,跳转数是 n^3+n^2-n 次)
而如果以顺序 ikj 实现,它将 C 中元素一行一行计算。当计算 C 中任一行的第一个元素时,先访问 A 中相应行的第一列元素,和 B 中第一列的第一行元素,然后 B 不断往右挪(不间断),算完后跳转到下一行(如果二维数组则间断一次,一维数组不间断),此时 A 往右挪一个元素(不间断)。依次这样挪动,这样算完 C 的这一行元素后,恰好按顺序将 B 遍历一遍,间断了 n 次(一维数组是 1 次),且恰好从左往右遍历了 A 的相应行,间断了 1 次(一维数组没有间断),加起来是 n+1 次(一维数组是 1 次)。故算完 C 的所有 n 行后,跳转了 n^2+n 次(一维数组是 n 次)。刚才没有算 C 的跳转,算上后跳跃数是 2n^2+n 次(一维数组是 n^2 次)。
(我上面这块写的很乱,找时间修改一下)
由此可见: