作业题目

提交内容一:平台信息

  • 本次作业在Windows平台及MacOs平台上均测试了homework1.cpp的运行,具体平台信息如下

Windows平台

  • 系统:Win11-23H2

  • CPU:i9-13900HX(x86)

  • 内存:64G
  • IDE:CLion
  • 编译器:MinGW

MacOs平台

  • 系统:MacOs-14.3
  • CPU:M2(arm)
  • 内存:16G
  • IDE:CLion
  • 编译器:Clang

提交内容二:代码运行时间

  • 本次作业分别在两个平台上使用-O0 -O1 -O2 -O3编译homework1.cpp,并记录四种优化条件下代码的运行时间,具体结果如下

Windows平台

-O0编译

-O1编译

-O2编译

-O3编译

数据总览

Win-MinGW Vecadd Timing/us Matrix Multiply Timing/us Pi Timing/us
-O0 3224 2557602 4074
-O1 2669 2310377 1001
-O2 2009 2344274 1017
-O3 2000 2358837 1651

MacOs平台

-O0编译

-O1编译

-O2编译

-O3编译

数据总览

Mac-Clang Vecadd Timing/us Matrix Multiply Timing/us Pi Timing/us
-O0 8858 6661187 6147
-O1 0 0 2358
-O2 0 1 2390
-O3 1 0 2592
  • 可以发现在Mac端,使用-O1及以上优化级别进行编译时,Vecadd Timing及Matrix Multiply Timing均为0us或1us,因此可能是这两个函数根本没被运行。查看代码发现,原实验代码中Vecadd和Matrix Multiply函数计算完c[1024*1024]及cc[1024*1024]后,这两个数组都没有再被使用过,因此可能是Clang编译器识别到这一情况后直接将这两个函数视为无用代码在编译时进行了删除。因此修改源代码,在计算完c[1024*1024]及cc[1024*1024]后分别输出一下c[0],让编译器在编译时不删除这两个函数的内容。以下是修改源代码后再次测试的结果

MacOs平台修改源码后再次测试

-O1编译

-O2编译

-O3编译

数据总览

Mac-Clang Vecadd Timing/us Matrix Multiply Timing/us Pi Timing/us
-O0 8858 6661187 6147
-O1 6532 2105799 1745
-O2 5817 2105989 1564
-O3 2585 2104009 1601

提交内容三:优化方法分析

  • 以Windows平台,Vecadd函数为例进行分析

-O0

  • 不进行任何优化

-O1

  • 在 -O0中,vecadd 函数的循环是这样的:
.L7:
  movq    -8(%rbp), %rax
  cmpq    40(%rbp), %rax
  jnb     .L6
  movq    -8(%rbp), %rax
  leaq    0(,%rax,8), %rdx
  movq    16(%rbp), %rax
  addq    %rdx, %rax
  movsd   (%rax), %xmm1
  movq    -8(%rbp), %rax
  leaq    0(,%rax,8), %rdx
  movq    24(%rbp), %rax
  addq    %rdx, %rax
  movsd   (%rax), %xmm0
  movq    -8(%rbp), %rax
  leaq    0(,%rax,8), %rdx
  movq    32(%rbp), %rax
  addq    %rdx, %rax
  addsd   %xmm1, %xmm0
  movsd   %xmm0, (%rax)
  addq    $1, -8(%rbp)
  jmp     .L7
.L6:
  movl    $0, %eax
  addq    $16, %rsp
  popq    %rbp
  ret
  • 在 -O1中,vecadd 函数的循环是这样的:
.L5:
  movsd  (%rcx,%rax,8), %xmm0
  addsd  (%rdx,%rax,8), %xmm0
  movsd  %xmm0, (%r8,%rax,8)
  addq    $1, %rax
  cmpq    %rax, %r9
  jne     .L5
.L4:
  movl    $0, %eax
  ret
  • 循环展开:在 -O1 中,循环体被简化了。在 -O0 中,我们看到了对索引的计算(leaq 0(,%rax,8), %rdx),然后使用这个索引来访问数组。在 -O1 中,这个计算被直接内联到循环体中,减少了对寄存器的依赖和循环控制的开销。
  • 寄存器使用:在 -O1 中,编译器对寄存器的使用进行了优化,相比于-O0更加高效
  • 循环简化:在 -O1 中,循环控制(addq $1, %rax ,cmpq %rax, %r9)和条件跳转(jne .L5)相较于-O0中的代码而言更加简化,这有助于提高循环的执行效率。

-O2

  • -O2的vecadd函数基本与-O1中一样,证明-O1已经对该函数进行了足够的简化,或该函数较为简单,无须再次优化

-O3

  • 在 -O2中,vecadd 函数的汇编代码是这样的:

    .L6:
      movsd  (%rcx,%rax,8), %xmm0
      addsd  (%rdx,%rax,8), %xmm0
      movsd  %xmm0, (%r8,%rax,8)
      addq    $1, %rax
      cmpq    %rax, %r9
      jne     .L6
    .L5:
      xorl    %eax, %eax
      ret
  • 在 -O3中,vecadd 函数的汇编代码是这样的:

    .L7:
      movq    (%rcx,%rax), %xmm0
      movq    (%rdx,%rax), %xmm1
      movhpd  8(%rcx,%rax), %xmm0
      movhpd  8(%rdx,%rax), %xmm1
      addpd   %xmm1, %xmm0
      movlpd  %xmm0, (%r8,%rax)
      movhpd  %xmm0, 8(%r8,%rax)
      addq    $16, %rax
      cmpq    %rax, %r10
      jne     .L7
      movq    %r9, %rax
      andq    $-2, %rax
      cmpq    %rax, %r9
      je      .L5
      movsd   (%rcx,%rax,8), %xmm0
      addsd  (%rdx,%rax,8), %xmm0
      movsd   %xmm0, (%r8,%rax,8)
    .L5:
      xorl    %eax, %eax
      ret
  • 向量化操作:在 -O3 中,编译器添加了 movhpd 指令的使用,这表明编译器可能在尝试利用 SSE2 指令集进行向量化操作。这可以显著提高数据处理的效率,因为它允许同时处理多个数据。

  • 循环展开:在 -O3 中,循环体被进一步展开,以减少循环控制的开销。例如,movhpd 指令用于同时加载和存储两个双字(64位)浮点数,这减少了内存访问次数。
  • 条件分支优化:在 -O3 中,编译器对条件分支进行了优化,例如通过使用 setnb 和 seta 指令来优化条件跳转。这有助于提高分支预测的准确性。
  • 循环控制逻辑:在 -O3 中,循环控制逻辑更加复杂,这表明编译器可能在尝试更精细地控制循环的执行,以减少不必要的计算和内存访问。

总结

  • -O0:不进行优化
  • -O1:主要对代码的分支、表达式、循环体等进行优化
  • -O2:尝试更多的寄存器级和汇编指令级的优化,增大调试难度
  • -O3:在O2的基础上对循环体等进行更多精细、激进的优化,可能会使汇编代码的逻辑更加复杂,加大精度的同时可能会增大运行时间及调试难度
  • Clang编译器的优化更为激进,会删除不影响程序运行的代码段(例如定义后未使用的变量等),因此在本次作业中需要修改源代码才能进行运行时间的测量,否则编译器会直接删除main函数中Vecadd以及Matrix Multiply函数的调用

自评分及理由

自评分

  • 10分

评分理由

  • 在Windows及MacOs双平台完成作业
  • 收集双平台数据并绘制总览图表
  • 根据汇编代码进行不同优化级别的优化方法分析
  • 分析Clang(Mac)进行-O1以上级别的编译时代码运行时间为0的原因
  • 对比分析MinGW(Win)及Clang(Mac)的优化策略