局部性原理: CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
计算机存储结构内存,一级缓存,二级缓存,寄存器等。缓存是用来存放从内存中取出的指令和数据,用来提高cpu访问内存的速度 而寄存器是用来存放cpu在执行指令时所需要的操作数或执行结果寄存器的内容可以通过编程控制,也就是说对程序员而言是可见的,而缓存不能通过编程控制,对程序员而言是透明的。缓存的访问速度是远远快于内存的,计算机执行运算时总是把用到内存的相关的数据放到缓存中以便于下次用到时直接读取缓存数据,缓存只能存放小部分数据,而且缓存的数据也不可能百分百命中,如果缓存不命中,那么CPU 只能从内存再次读取数据。所以提高程序的局部性,对于加快程序的执行速度有帮助的。
二位数组在内存中是以线性存放,以下就是int[5,4] myArray的数组内容如下
0, 0, 0 ,0
1, 1,1 , 1
2,2, 2, 2
3, 3, 3, 3
4, 4, 4, 4
它在内存中是以以下方式存放的:
0, 0, 0 ,0 1, 1,1 , 1 ,2,2, 2, 2 ,3, 3, 3, 3 ,4, 4, 4, 4
如果计算数组myArray所有元素之和,有两种方式:
1 按行依次访问myArray 数组元素,并依次求和
2 按列依次访问myArray数组元素,并依次求个
这两种方式在性能上有区别吗?为什么
答案:有区别,按行访问数组元素求和总是快于按列访问数组元素的求和。
请看如下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; namespace hbb0b0.dotnetTrace { class Program { static void Main(string[] args) { Stopwatch watch = new Stopwatch(); string content = string.Format ("Main: at:{0}",DateTime.Now); Trace.WriteLine(content); watch.Start(); NumberTest numberTest = new NumberTest(); for (int i = 0; i < 5;i++ ) { //按行方式求和 numberTest.CalcByRowFirst(); //按列方式求和 numberTest.CalcByColumnFirst(); } watch.Stop(); //统计总的代码的执行时间 content = string.Format("Main cost:{0}", watch.ElapsedMilliseconds); Trace.WriteLine(content); Console.Read(); } } class NumberTest { //测试数组9000*9000的数组 private int[,] m_intarray = new int[9000, 9000]; //初始化数据 public NumberTest() { string content = string.Format("NumberTest time:{0} GetUpperBound(0):{1} GetUpperBound(1):{2}", DateTime.Now.ToString(), m_intarray.GetUpperBound(0), m_intarray.GetUpperBound(1)); Trace.WriteLine(content); Trace.WriteLine("init data"); for (int i = 0; i <= m_intarray.GetUpperBound(0); i++) { for (int j = 0; j <= m_intarray.GetUpperBound(1); j++) { m_intarray[i, j] = i + j; } } } /// <summary> /// 按行优先方式求和数组 /// </summary> public void CalcByRowFirst() { Stopwatch watch = new Stopwatch(); watch.Start(); long result = 0; for (int i = 0; i < m_intarray.GetUpperBound(0); i++) { for (int j = 0; j < m_intarray.GetUpperBound(1); j++) { result += m_intarray[i, j]; } } watch.Stop(); //统计代码的执行时间 string content = string.Format("CalcByRowFirst:Cost:{0} result:{1}", watch.ElapsedMilliseconds, result); Trace.WriteLine(content); } /// <summary> /// 按列优先方式求和数组 /// </summary> public void CalcByColumnFirst() { Stopwatch watch = new Stopwatch(); watch.Start(); long result = 0; for (int i = 0; i < m_intarray.GetUpperBound(1); i++) { for (int j = 0; j < m_intarray.GetUpperBound(0); j++) { result += m_intarray[j, i]; } } watch.Stop(); //统计代码的执行时间 string content = string.Format("CalcByColumnFirst:Cost:{0} result:{1}", watch.ElapsedMilliseconds, result); Trace.WriteLine(content); } } }
App.Config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true">
<listeners>
<clear/>
<add type="System.Diagnostics.TextWriterTraceListener" initializeData="tracelog" name="txtTrace"></add>
<add type="System.Diagnostics.ConsoleTraceListener" name="ConsoleTrace"></add>
</listeners>
</trace>
</system.diagnostics>
</configuration>
输出结果如下:
Main: at:2013/2/20 11:06:01
NumberTest time:2013/2/20 11:06:01 GetUpperBound(0):8999 GetUpperBound(1):8999
init data
CalcByRowFirst:Cost:3991 result:728676044998
CalcByColumnFirst:Cost:8264 result:728676044998
CalcByRowFirst:Cost:4816 result:728676044998
CalcByColumnFirst:Cost:6969 result:728676044998
CalcByRowFirst:Cost:4952 result:728676044998
CalcByColumnFirst:Cost:8203 result:728676044998
CalcByRowFirst:Cost:5006 result:728676044998
CalcByColumnFirst:Cost:7134 result:728676044998
CalcByRowFirst:Cost:4127 result:728676044998
CalcByColumnFirst:Cost:6388 result:728676044998
Main cost:62952
Main: at:2013/2/20 11:09:51
NumberTest time:2013/2/20 11:09:51 GetUpperBound(0):8999 GetUpperBound(1):8999
init data
CalcByRowFirst:Cost:4939 result:728676044998
CalcByColumnFirst:Cost:7807 result:728676044998
CalcByRowFirst:Cost:4956 result:728676044998
CalcByColumnFirst:Cost:8167 result:728676044998
CalcByRowFirst:Cost:4894 result:728676044998
CalcByColumnFirst:Cost:7592 result:728676044998
CalcByRowFirst:Cost:3833 result:728676044998
CalcByColumnFirst:Cost:7082 result:728676044998
CalcByRowFirst:Cost:4121 result:728676044998
CalcByColumnFirst:Cost:8109 result:728676044998
Main cost:66289
经过多次比对,按行访问数组元素求和总是快于按列访问数组元素的求和。
造成这种情况的原因就是CPU 局部性原理。
CPU在访问数组元素的时候,总会把他相邻的元素读取出来并放入到缓存中。假设一次访问内存中的数组元素,它会把后续的99个元素放到缓存中,那么CPU每读取
一次内存就可以减少后续的99次读取内存。
按行访问的内存读取次数就是
900*900
而按列访问内存的读取次数就是
9000*9000
按列访问同样会读取一个数组元素后缓存后续的99个数组元素,但是按列求和数组用到的数据并不是缓存的的99个元素,虽然缓存了99个元素,但是后续的99次操作一次缓存
也不会命中,所以按列访问数组元素求和是会访问内存9000*9000。
所以输出结果中行读取比列读取快也是自然而然的事情。