【第 4 周】深入理解计算机系统共读心得体会

所在小组

第五组

组内昵称

陈松彬

心得体会

Chapter3 section2

Author: @陈松彬

Date: 2020-10-18

section2 主要是3.8 ~ 3.12

一、GCC对数组访问的优化

书中给出的公式:对于数组T D[R][C],元素D[i][j]的内存地址为&D[i][j] = 数组起始地址 + i * C + j

贴出书中一个例子:

# define N 16
typedef int fix_matrix[N][N]
// 计算矩阵A的第i行跟 矩阵B的第k列 的向量积
int fix_prod_ele(fix_matrix A, fix_matrix B, long i, long k) {
   long j;
   int result = 0;
   for (j=0; j<N; j++){
       result += A[i][j] * B[j][k];
   }
   return result;
}

按照我们写的代码:每次循环总共会有3个乘法操作:

  • 计算元素A[i][j]的地址 = A的起始地址 + i * N + j
  • 计算元素B[j][k]的地址 = B的起始地址 + j * N + k
  • A[i][j] * B[j][k]

不使用gcc优化:

gcc -Og -S fix_prod_ele_opt.c -o Og.s

使用gcc优化:

gcc -O1 -S fix_prod_ele_opt.c -o o1.s

优化过后汇编代码:

a) 我们可以看到循环只用了一个乘法指令

b) 可以看到对于数组元素访问,本来是需要用到乘法计算数组元素地址的,结果都被GCC优化掉了,改成通过将地址加一个常量:

  • addq $4, %rdi # Aptr ++
  • addq $64, %rdx # Bptr += N

c) 再次证明乘法操作是比较耗时的,GCC编译器会千方百计减少乘法操作

二、数据对齐

  • 地址必须是某个值K的倍数,这个K

  • 为什么需要内存对齐:提高CPU从内存获取数据的效率

  • 应用:
    a) 利用内存对齐,通过对结构体中的某些字段的顺序进行重新排序,可以减少padding,进而使得一个结构体占用的内存减小。

    b) 这个优化貌似编译器不会帮我们做,我们可以写一个脚本,可以对我们的定义的结构体的字段进行重新排序。这个算法也很简单,只要把占用字节数多的字段排在前面即可。

    c)比如

    # 16字节
    type TestStruc struct {
        A int   # 0
        B char  # 4
        C int   # 8
        D char  # 12
    }
    
    # 12字节
    type TestStruc struct {
        A int  0
        C int  4
        B char  8
        D char  9
    }
    
  • 疑问1: 对于一个指令movb 内存,到寄存器;是如何传输的?

    猜想:

    (1)CPU执行指令,movb 内存地址,某个寄存器

    (2)根据内存地址 找CPU cache 【这个是如何映射的?】

    (3.1 )如果命中CPU cache

    (3.2) 如果没有命中CPU Cache,从内存加载数据到CPU Cache

  • 疑问2: 对于读下面中蓝色的8字节数据:

    a) 为什么要执行两次内存操作(1)0-7(2)8-15

    b) 为什么不能一次性搞定:3-10

三、SIMD指令

  • SIMD 单指令多数据,也就是说同一个指令可以对多个数据进行操作。eg: 对于向量x = (1,1,1,1); y=(2,2,2,2),对于普通指令,需要执行四次加法操作,使用SIMD指令只需1个指令完成操作,x + y = (1,1,1,1)+(2,2,2,2) = (3,3,3,3)
  • 发展路径:MMX → SSE → AVX
  • 这些指令具有特殊的寄存器,寄存器存储大小不断变大,从最早的 64位 → 128位 → 256位 → 512位
  • 在2000年Pentium中引入了SSE2,开始使用这些寄存器,支持浮点计算与操作