Pentium 及 MMX 代码的优化

高速缓存优化

[第四节指令选择][第六节:内存优化]


  高速缓存可的对你的应用程序的性能产生戏剧性的影响. 在深刻理解高速缓存的工作原理后, 你可对代码结构进行安排, 最大限度地发挥高速缓存性能. 有关高速缓存结构的内容. 参见(原书 2.2节).

5.1 线读入顺序

  当对一个可高速缓冲的数据访问时, 若该数据不在数据高速缓存中, 将使整个高速缓存线从外部内存带入高速缓存, 这就称之为线读入. 对于奔腾或动态执行(P6-系列)处理器, 这些数据按下列成组顺序以4个8字节段组成的成组读入.
第一个地址第二个地址第三个地址第四个地址
0H8H10H18H
8H0H18H10H
10H18H0H8H
18H10H8H0H

  数据可以按它们到达的顺序有效地使用. 如果一个数据组按串行顺序读出, 那么可按串行顺序进行访问. 因此, 每个数据项可在它从内存到达时即被使用.

5.2 在高速缓存线中的数据对齐

  大小为32字节倍数的数组应在高速缓存线的起始位置. 以32字节边界对齐, 将充分发挥按序线读入的优势, 并匹配高速缓存线大小. 大小非32字节倍数的数组应以32或16字节边界开始 (在高速缓存线的开始或中间). 为了按32或16边界对齐, 需要对数据进行填充. 如果必要, 尽量按填充后的空间来定位数据(变量或常量).

5.3 写分配效果

  动态执行(P6-系列)处理器有一个"按读分配写"的高速缓存, 对应于奔腾处理器的"无写分配, 通过写失败而写"的高速缓存.

  在动态执行(P6-系列)处理器中, 写操作发生但被写部分不在高速缓存时, 整个32字节高速缓存线被读入.在奔腾处理器中,当被写部分不在高速缓存时, 仅简单地写到内存中去.

  由于连序存贮操作被合并为突发写, 且将数据保存在高速缓存中可为后继的读取操作使用, 使写分配通常是有利的. 这就是动态执行(P6-系列) 处理器采用这种写策略的原因, 也是一些奔腾处理器的系统在设计 L2 Cache时实现这种方式的原因.

  在以下情况下, 写分配有如下缺点:

  当在一个应用程序中有大量的写操作, 如下面例子所示, 跨距大于32字节高速缓存线且数组为大数组时, 对动态执行 (P6-系列)处理器的每个写操作将使整个高速缓存线被读取.另外, 这种读取将替换掉一条(有时两条)不用或很少用的高速缓存线.

  这样将导致每次存贮时增加一次对高速缓存线的读取, 并降低程序的执行速度. 当一个程序中有大量的写操作时, 将使性能降低。厄拉多塞筛选程序是一个说明这种高速缓冲效果的简单例子. 在这个例子中, 一个大数组不断地按增大的步长将其特定的值赋0.

  注意这仅为一个表现高速缓存效果的简单例子. 在代码中可使用很多其它的优化方法.

  厄拉多塞筛选例子:


    boolean array [2..max]
    for (i=2; i<max; i++) {
	    array:=1;
		}
    for (i=2; i<max; i++) {
        if (array[i]) {
            for(j=2; j<max; j+= i) {
			   array[j]:=0;  /* 这里我们对内存赋0产生了
                                j循环内对高速缓存线的读取*/
			}
		}
	}

  对这个特定的例子来说, 有两种有效的优化方法. 第一种是通过改用位数组来减少数组大小, 目的是降低 Cache线的读取次数. 每二种是通过检查前一次写的值, 降低对内存的读写次数(波动较大高速缓存线).

5.3.1 优化方法1:布尔

  在上面的程序中,"Boolean" 是一个字符型数组.在某些程序中, 更好的方法是把"boolean" 数组变成位数组, 这样可以执行读 一一修改一一写操作(因为高速缓存规程将每个读操作变成 读一一修改一一写). 但在本例中, 由于大多数的步长大于256比特 (一个高速缓存线的位数). 故不能有效地提高性能.

5.3.2 优化方法2: 写前检查

  另一种优化方法是在写前检查该值是否已经是0.


    boolean array[2..max]
    for(i=2; i<max;i++)  {
        array:=1;
		}
    for(i=2; i<max; i++) {
        if(array[i]) {
           for (j=2; j<max; j+=i) {
               if(array[j]!=0) {   /* 检测该值是否已为0 */
                    array[j]:=0;
				}
			}
		}
	}

  由于在大多数时候筛选程序的数据已经是0, 所以可以把对外部总线的驱动次数降低一半.

  通过预先检查, 可以仅使用一个猝发总线周期读数据, 并为每个不需要再写的缓存线节省一个猝发总线周期. 由于对已修改的高速缓存线不再需要回写, 可节省下额外的时钟周期.

  注意 本操作仅对 P6-系列的处理器有意义, 不能增加奔腾处理器的性能. 因此, 本操作不具备通用性. 由于顺序存贮被合并为猝发写, 高速缓存中的数据为下一次读取而保留, 所以写分配在大多数系统中是一种通用的改善性能的方法, 这也是为什么 P6系列处理使用该策略, 而一些基于奔腾处理器的系统在 L2高速缓存中实现它的原因.


云风工作室 制作