转自evol128  特此表示感谢

http://evol128.is-programmer.com/posts/35453.html

问题的出处:http://stackoverflow.com/questions/11413855/why-is-transposing-a-matrix-of-512x512-much-slower-than-transposing-a-matrix-of

事情的起因是这样的,先看下面这段代码:

#define SAMPLES 1000
#define MATSIZE 512 #include <time.h>
#include <iostream>
int mat[MATSIZE][MATSIZE]; void transpose()
{
for ( int i = ; i < MATSIZE ; i++ )
for ( int j = ; j < MATSIZE ; j++ )
{
int aux = mat[i][j];
mat[i][j] = mat[j][i];
mat[j][i] = aux;
}
} int main()
{
//initialize matrix
for ( int i = ; i < MATSIZE ; i++ )
for ( int j = ; j < MATSIZE ; j++ )
mat[i][j] = i+j; int t = clock();
for ( int m = ; m < SAMPLES ; m++ )
transpose();
int elapsed = clock() - t; std::cout << "Average for a matrix of " << MATSIZE << ": " << elapsed<<std::endl;
return ;
}

很普通的一个求矩阵转置的程序。但是,当MATSIZE取512和513的时候,出现了非常有意思的结果:

512 平均 2.19ms

513 平均 0.57ms

很让人惊讶吧,513竟然比512快。更进一步的研究发现,size=512的时候,运算速度会比同数量级的其它数字慢很多很多。这是怎么一回事呢?

stackoverflow上大牛给的解答非常正确,但是这次,我不想做翻译了。我从Professor Sibert那里,从Professor Goel那里,学到的知识,足够帮我解决这个问题了,我不是一个人。下面是我的解答:

很容易就联想到,造成这个问题的原因是CPU cache,我们有很多种方式来存储cache,具体可以参考这里

原作者没有给出他的CPU型号,但是如今的pc几乎都是采用的set associative的cache结构,下面我用2-way set associate来做例子,讲解一下cache的工作原理。

(图片取自Professor Sibert的讲义,这可是纯ascii画的哦= =)

一个内存地址,可以划分为block,tag,word,byte 4个部分。10bits的block,对应了1024个cache set,内存地址的block固定了,就必须存储在相应的set里面,这样可以把查询cache的事件从O(n)缩短为O(1)。

举个例子,block是1023(1111111111),你的数据就放在第1023个set里面。可能有人会觉得奇怪,为什么block不是取的最前面的10bits,这当然是有道理的,通常在内存里数据都是连续存放的,就是说,同一段程序用的数据,他们前10位几乎都是一样的,如果用前10位来定位block,那么collision的发生率非常高,cache效率非常低下,所以才选了后面的10位来定位block。

当然,每个set里面有多条记录,2-way是2条,你得遍历这两条记录,比较前面50位的tag,如果tag一样,并且Valid bit(V)=1,那么恭喜你,你的数据在cache里面,接着就可以通过word和byte来取数据了。

如果遍历完这两条记录,还是没有找到tag的话,那么很遗憾,你的数据不在cache里,得从内存里读。从内存里获取相应的数据,然后把它存到对应的cache set里,如果set里有空位的话最好,如果没有的话,用LRU来替换。因为一个set里只有2条数据,所以实现LRU仅仅需要一个额外bit就可以了,非常高效。

好了,背景知识介绍的差不多了,让我们回到这个问题上来。为什么512大小的矩阵,会比其它数字慢那么多?

让我们来计算一下,512x512的int矩阵,在内存里是连续存放的。每个cache line是16bytes,对应4个int,所以一个n阶矩阵的row可以填充n/4个cache set。假设第一个数据a[0][0]正好对应cache set 0,那么其中每一个数据a[i][j]对应的cache set是(512i+j)/4%1024=(128i+j/4)%1024。可以看到,前面的系数正好可以整除。很不巧的是,在进行矩阵转置的运算时,在第2个for循环中,我们需要依次访问每一个row中对应i的值。这样会造成下面的结果:假设i=0,set(a[0][0])=0, set(a[1][0])=128, set(a[2][0])=256...set(a[7][0])=896,set(a[0][0])=0,后面开始重复了,到a[15][0]的时候刚好填完整个cache的所有128整数倍的set,当读取a[16][0]的时候,将会发生replace,把a[0][0]从cache里移除。这样,当源程序的i=1时,将完全重复i=0的计算过程,每次取数据都需要先从memory读到cache中来,cache的作用完全没有体现。

而当size=513的时候,事情就不一样了,mat[i][j]对应的cache set是(513i+j)/4%1024,前面的系数除不尽了,每递增4次结果会比size=512时偏差1。例如:set(a[0][0])=0, set(a[1][0])=128, set(a[2][0])=256,set(a[3][0])=384, set(a[4][0])=513...这样就很微妙的把cache set给错开了。a[16][0]不在第0行而是第4行,不会覆盖之前的数据。即使将全部的a[0-15][i]都读入cache,也不会发生碰撞。之后,由于一个cache有4个word,a[0-15][i+1],a[0-15][i+2],a[0-15][i+3]也同时被读进cache里了,所以计算i+1,i+2,i+3时,仅仅需要读对应行的数据就可以了,同一行的数据都是连续的,所以碰撞率很低。这个计算过程很好的利用了cache,如果不考虑其他因素(实际上,这个已经是影响运行时间的最大因素了),理论上我们可以节省75%的运行时间,可以看到,这个理论预测是和提问者给的数据相符合的。

总之,当你的data size是128的整数倍的时候,得特别小心,搞不好cache collision就把你的程序给拖慢了呢

Update 1: 原代码有逻辑错误,这点大家都不要吐槽了,代码不是我写的= =

Update 2:帅哥问我,为什么可以加速这么多。这个循环包括4次读cache的操作,2次写cache的操作,以及0-2次replace操作。每次replace操作会有一次memory read,有可能会有memory write(假设它是write back)。前面的读写cache时间和读写内存相比,几乎可以忽略,对效率产生显著影响的是后面的内存读写。如果cache的hit率高了,那么内存读写的次数就少了,程序运行时间是会产生很大影响的

Update 3:当然,具体效果还视乎CPU架构而定,我自己试验的只有节省25%左右时间

Update 4: 有人提出了用划分矩阵(把大矩阵分成若干个小矩阵分别计算)的方法来求转置。划分矩阵可以解决类似的问题(譬如说求两个矩阵乘积),但是对解决这个问题没有任何帮助。因为求转置的时候,每个数据只用到了一次,没有重复访问;即便划分成更小的矩阵,在cache里面的位置也没有发生改变。

注:我在Windows xp sp2, VC++ 6.0 SP2, Intel Pentium G630 双核2.7GHz 环境下测试,512和513区别不大,都是1秒左右。

但是换成1024 和 1025测试,发现1024时,耗时30毫秒;1025耗时仅仅11毫秒。

<转>为什么转置一个512x512的矩阵,会比513x513的矩阵慢很多?的更多相关文章

  1. 一个N*M的矩阵,找出这个矩阵中所有元素的和不小于K的面积最小的子矩阵

    题目描述: 一个N*M的矩阵,找出这个矩阵中所有元素的和不小于K的面积最小的子矩阵(矩阵中元素个数为矩阵面积) 输入: 每个案例第一行三个正整数N,M<=100,表示矩阵大小,和一个整数K 接下 ...

  2. Java练习小题_求一个3*3矩阵对角线元素之和,矩阵的数据用行的形式输入到计算机中 程序分析:利用双重for循环控制输入二维数组,再将a[i][i]累加后输出。

    要求说明: 题目:求一个3*3矩阵对角线元素之和,矩阵的数据用行的形式输入到计算机中 程序分析:利用双重for循环控制输入二维数组,再将 a[i][i] 累加后输出. 实现思路: [二维数组]相关知识 ...

  3. (转)思考:矩阵及变换,以及矩阵在DirectX和OpenGL中的运用问题:左乘/右乘,行优先/列优先,...

    转自:http://www.cnblogs.com/soroman/archive/2008/03/21/1115571.html 思考:矩阵及变换,以及矩阵在DirectX和OpenGL中的运用1. ...

  4. Codevs 1287 矩阵乘法&&Noi.cn 09:矩阵乘法(矩阵乘法练手题)

    1287 矩阵乘法  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 小明最近在为线性代数而头疼, ...

  5. leetcode.矩阵.766托普里茨矩阵-Java

    1. 具体题目 如果一个矩阵的每一方向由左上到右下的对角线上具有相同元素,那么这个矩阵是托普利茨矩阵.给定一个 M x N 的矩阵,当且仅当它是托普利茨矩阵时返回 True. 示例 1: 输入: ma ...

  6. 编程计算2×3阶矩阵A和3×2阶矩阵B之积C。 矩阵相乘的基本方法是: 矩阵A的第i行的所有元素同矩阵B第j列的元素对应相乘, 并把相乘的结果相加,最终得到的值就是矩阵C的第i行第j列的值。 要求: (1)从键盘分别输入矩阵A和B, 输出乘积矩阵C (2) **输入提示信息为: 输入矩阵A之前提示:"Input 2*3 matrix a:\n" 输入矩阵B之前提示

    编程计算2×3阶矩阵A和3×2阶矩阵B之积C. 矩阵相乘的基本方法是: 矩阵A的第i行的所有元素同矩阵B第j列的元素对应相乘, 并把相乘的结果相加,最终得到的值就是矩阵C的第i行第j列的值. 要求: ...

  7. HDU2256&&HDU4565:给一个式子的求第n项的矩阵快速幂

    HDU2256 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2256 题意:求(sqrt(2)+sqrt(3))^2n%1024是多少. 这个题算是h ...

  8. R语言将字符串矩阵转化为数值型矩阵

    这是原始数据的格式,当运行完下面的命令的时候,结果如下图 x=read.table("C:/Users/Administrator/Desktop/s1.txt") x=as.ma ...

  9. 经典矩阵快速幂之一-----poj3233(矩阵套矩阵

    题意:给你一个矩阵A,求S=A+A^2+A^3+...+A^k. 其实这个当时我看着毫无头绪,看了他们给的矩阵发现好!精!妙! 我们这样看 是不是有点思路! 没错!就是右上角,我们以此类推可以得到A+ ...

随机推荐

  1. 关于JAVA面向对象基础整理以及个人的理解(适合初学者阅读)

    JAVA的基础语法等都跟C有很大的相似之处,所以这里就不多啰嗦了.直接从数组开始. 数组: 1.声明 int [] a; 在声明的过程中,并没有分配空间给数组.我们可以在声明的同时,用new来分配相应 ...

  2. MJRefreshFooterView

    实例化header和footer _header = [MJRefreshHeaderView header]; _header.scrollView = _tableView; 设置header和f ...

  3. 如何学习C++[转]

    关于学C++, 我向你推荐一些书(当然能够结合课内项目实践更好) 1.The C++ Programming Language(Bjarne Stroustrup)2. Inside The C++ ...

  4. 谷歌笔试题——排序,只允许0和其他元素交换

    2.2 长度为n的数组乱序存放着0至n-1. 现在只能进行0与其他数的swap,请设计并实现排序. 这题有一个隐含条件:即数组元素是连续的,即0--n-1,当你排好序后,你会发现数组元素和该元素的下标 ...

  5. BZOJ 4027 [HEOI 2015] 兔子与樱花 解题报告

    这个题看起来好神的感觉.实际上也好神... 我们可以考虑设 $f_u$ 表示以 $u$ 为根的子树中最多能删多少个点, 再设 $g_u$ 表示以 $u$ 为根的子树中删了 $f_u$ 个点之后,$u$ ...

  6. C++ new operator, delete operator, operator new, operator delete, new placement

    http://www.younfor.com/cpp-new-placement-new-operator-new.html http://www.cnblogs.com/luxiaoxun/arch ...

  7. SDUT 1269 走迷宫(BFS)

    点我看题目 题意:中文不详述. 思路 :上上上场比赛让一个BFS给虐了,上次比赛让一个三维的给废掉了.......所以急于从水题刷起......还因为数组开小了WA了5,6次 #include < ...

  8. 游戏文字自动断行需要,还得从 UTF-8 讲起

    UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码. UTF-8使用一至六个字节为每个字符编码(尽管如此,2 ...

  9. hdu4374One hundred layer (DP+单调队列)

    http://acm.hdu.edu.cn/showproblem.php?pid=4374 去年多校的题 今年才做 不知道这一年都干嘛去了.. DP的思路很好想 dp[i][j] = max(dp[ ...

  10. 利用逻辑运算符?"三个数字比大小

    static void Main(string[] args)        {            int a, b, c;            while (true)            ...