教程: 优化硬件利用率

本文更新于 2018.10.10

本文演示了如何分析硬件相关的性能问题, 如数据共享, cache miss, 分支预测失败等.

参考: https://software.intel.com/en-us/vtune-hw-issues-tutorial-lin-pdf

工作流:

../../_images/tutorial_hw.png

编译示例程序

本教程使用matrix示例, 位置在 /opt/intel/system_studio_2019/samples_2019/en/vtune_amplifier/C++/matrix_vtune_amp_xe.tgz, 将其解压至某目录, 进入matrix/linux目录运行make进行编译.

如果用intel编译器, 请先设置环境变量:

source /opt/intel/system_studio_2019/compilers_and_libraries_2019.0.120/linux/bin/compilervars.sh -arch intel64

然后运行make icc:

$ make icc
icc -g -O3 -DUSE_THR -DICC -debug inline-debug-info  -c ../src/util.c -D_LINUX
icc -g -O3 -DUSE_THR -DICC -debug inline-debug-info  -c ../src/thrmodel.c -D_LINUX
icc -g -O3 -DUSE_THR -DICC -debug inline-debug-info  -xSSE3  -c ../src/multiply.c -D_LINUX
icc -g -O3 -DUSE_THR -DICC -debug inline-debug-info  -xSSE3  -c ../src/matrix.c -D_LINUX
icc -g -O3 -DUSE_THR -DICC -debug inline-debug-info  util.o thrmodel.o multiply.o matrix.o  -o matrix.icc -lpthread -lm

更多帮助见matrix/readme.txt. 编译完成后运行程序, 可以看到执行时间:

$ ./matrix.icc
Addr of buf1 = 0x7fe9ed2b7010
Offs of buf1 = 0x7fe9ed2b7180
Addr of buf2 = 0x7fe9eb2b6010
Offs of buf2 = 0x7fe9eb2b61c0
Addr of buf3 = 0x7fe9e92b5010
Offs of buf3 = 0x7fe9e92b5100
Addr of buf4 = 0x7fe9e72b4010
Offs of buf4 = 0x7fe9e72b4140
Threads #: 16 Pthreads
Matrix size: 2048
Using multiply kernel: multiply1
Freq = 3.199968 GHz
Execution time = 16.780 seconds

创建vtune工程并运行分析

创建vtune工程, 分析类型为Microarchitecture Exploration, 并配置可执行程序路径和源代码搜索路径等:

../../_images/tutorial_hw_2.png

完成后点击开始按钮开始分析.

注解

进行分析前最好通过BIOS设置关闭超线程支持, 否则对性能数据收集有影响. 性能分析工作完成后记得改回来

分析代码

分析完成后概览信息如下:

../../_images/tutorial_hw_3.png

其中可见CPI(Cycles per Instructions Retired) Rate和内存访问有性能问题.

切换到”Bottom-Up”视图, 可以看到multiply1函数占用了大量CPU时间, 需要优化:

../../_images/tutorial_hw_4.png

双击函数行打开源代码窗口, 可以看到第51行的代码, 它在进行矩阵运算时对内存的访问比较低效.

../../_images/tutorial_hw_5.png

优化代码

编辑multiply.h, 将MULTIPLY宏定义替换为multiply2, 表示使用multiply2函数, 此函数使用了”loop interchange”机制来优化内存访问.

// Step 2: Loop interchange
// Add compile option for vectorization report Windows: /Qvec-report3 Linux -vec-report3
for(i=tidx; i<msize; i=i+numt) {
    for(k=0; k<msize; k++) {
#pragma ivdep
        for(j=0; j<msize; j++) {
            c[i][j] = c[i][j] + a[i][k] * b[k][j];
        }
    }
}

其中, #pragma ivdep告诉intel编译器忽略假定的矢量依赖(Ignore assumed Vector DEPendencies), 它将令编译器启用SSSE.

保存修改后重新编译matrix, 运行:

$ ./matrix.icc
Addr of buf1 = 0x7f9650a20010
Offs of buf1 = 0x7f9650a20180
Addr of buf2 = 0x7f964ea1f010
Offs of buf2 = 0x7f964ea1f1c0
Addr of buf3 = 0x7f964ca1e010
Offs of buf3 = 0x7f964ca1e100
Addr of buf4 = 0x7f964aa1d010
Offs of buf4 = 0x7f964aa1d140
Threads #: 16 Pthreads
Matrix size: 2048
Using multiply kernel: multiply2
Freq = 3.149062 GHz
Execution time = 1.972 seconds

可见执行时间从16.780秒缩短到1.972秒, 提升了8倍还多.

进一步分析和优化

对修改后的代码再进行一次Microarchitecture Exploration分析, 概要信息如下:

../../_images/tutorial_hw_6.png

打开”Bottom-up”示图, 可以看到multiply2函数仍有优化空间:

../../_images/tutorial_hw_7.png

打开源代码视图, 并打开反汇编, 可以看到#pragam ivdep下面的代码已经被编译为矢量化指令:

../../_images/tutorial_hw_8.png

所有矢量化指令以p为后缀(如, mulp dx), 可以使用-vec-report3编码选项来让intel编译器生成编译器优化报告, 以发现哪些cycle未被矢量化及其原因.

再次修改代码, 将运算函数改为multiply3:

// Step3: Cache blocking
// Add current platform optimization for Windows: /QxHost Linux: -xHost
// Define the ALIGNED in the preprocessor definitions and compile option Windows: /Oa Linux: -fno-alias
istep = msize / numt;
ibeg = tidx * istep;
ibound = ibeg + istep;
mblock = MATRIX_BLOCK_SIZE;

for (i0 = ibeg; i0 < ibound; i0 +=mblock) {
    for (k0 = 0; k0 < msize; k0 += mblock) {
        for (j0 =0; j0 < msize; j0 += mblock) {
            for (i = i0; i < i0 + mblock; i++) {
                for (k = k0; k < k0 + mblock; k++) {
#pragma ivdep
#ifdef ALIGNED
    #pragma vector aligned
#endif //ALIGNED
                    for (j = j0; j < j0 + mblock; j++) {
                        c[i][j]  = c[i][j] + a[i][k] * b[k][j];
                    }
                }
            }
        }
    }
}

修改Makefile:

OPTFLAGS = -xHost -fno-alias -DALIGNED

重新编译运行:

$ ./matrix.icc
Threads #: 16 Pthreads
Matrix size: 2048
Using multiply kernel: multiply3
Freq = 2.399906 GHz
Execution time = 0.534 seconds

执行时间进一步缩短. 重新运行Microarchitecture Exploration分析, 概要信息如下:

../../_images/tutorial_hw_9.png

CPI Rate和内存利用率数据进一步提升.