使用 Intel® Advisor Python API 获取性能洞察
获取高质量数据以做出代码调优决策
良好的设计决策基于良好的数据
- 首先应该对哪些循环进行线程化和向量化?
- 性能提升是否值得付出努力?
- 线程化性能是否能随着更多核心的数量而扩展?
- 这个循环是否存在阻止向量化的依赖关系?
- 循环次数和内存访问模式是什么?
- 您是否使用了最新的 Intel® Advanced Vector Extensions 512 (Intel® AVX-512)
指令高效地进行了向量化?还是在使用旧的 SIMD 指令?
Intel® Advisor 是 Intel® Parallel Studio XE 的一部分,Intel® Parallel Studio XE 是 Intel 用于构建和现代化代码的全面工具套件。Intel Advisor 可以回答这些问题,以及更多的问题。您可以收集有关应用程序向量化和内存配置文件的有价值的程序指标。除了提供使用 GUI 和命令行量身定制的报告外,Intel Advisor 现在还提供了额外的灵活性,可以使用 Python* 来挖掘收集到的数据库并创建强大的新报告。
运行 Intel Advisor 时,它会将收集到的所有数据存储在专有数据库中,您现在可以使用 Python API 访问该数据库。这提供了一种灵活的方式来生成有关程序指标的定制报告。本文将介绍如何使用此新功能。
入门
要开始,您需要设置 Intel Advisor 环境。(本文中,我们在 Linux* 上运行了所有脚本,但 Intel Advisor Python API 也支持 Windows*。)
source advixe-vars.sh
接下来,要设置 Intel Advisor 数据,您需要运行一些收集。某些程序指标需要额外的分析,例如循环次数、内存访问模式和依赖关系。
advixe-cl --collect survey --project-dir ./your_project -- <your-executable-with-parameters> advixe-cl --collect tripcounts -flops-and-masks -callstack-flops --project-dir ./your_project -- <your-executable-with-parameters>
要运行 MAP 或依赖关系收集,您需要指定要分析的循环。您可以使用 Intel Advisor GUI 或通过命令行报告找到此信息。
advixe-cl --collect map –mark-up-list=1,2,3,4 --project-dir ./your_project -- <your-executable-with-parameters> advixe-cl --collect dependencies –mark-up-list=1,2,3,4 --project-dir ./your_project -- <your-executable-with-parameters>
最后,您需要将 Intel Advisor 参考示例复制到测试区域。
cp –r /opt/intel/advisor_2018/pythonapi/examples .
请注意,本文中我们运行的所有脚本都使用了 Intel Advisor 在 Linux 上提供的 Python。标准的 Python 发行版也同样适用。
使用 Intel Advisor Python API
我们提供的参考示例只是使用这种灵活访问程序数据方式所能实现的报告功能的一小部分。您可以运行 columns.py 示例来获取可用数据字段的列表。例如,在运行基本调查收集后,您可以看到表 1 中的指标。
Intel Advisor Python API 实战
让我们通过一个简单的示例来演示如何使用 Intel Advisor Python API 收集一些强大的指标。第一步是导入 Intel Advisor 库包。
import advisor
然后,您需要打开包含您收集到的结果的 Intel Advisor 项目。
project = advisor.open_project(sys.argv[1])
您还可以选择创建一个项目并运行收集。(在下面的示例中,我们只执行 `open_project`。)在此示例中,我们访问内存访问模式 (MAP) 收集的数据。我们通过以下代码行来完成此操作:
data = project.load(advisor.MAP)
加载此数据后,我们可以遍历表格并收集缓存利用率统计信息。然后,我们打印出收集到的数据:
import sys try: # First import the Advisor library import advisor except ImportError: sys.exit(1) # Open your Advisor project project = advisor.open_project(sys.argv[1]) # Load the Memory Access Pattern(MAP) data data = project.load(advisor.MAP) #Loop through the MAP data and gather information about cache utilization for site in data.map: site_id = site['site_id'] cachesim = data.get_cachesim_info(site_id) print(indent * 2 + 'Average utilization'.ljust(width) + ' = {:.2f}%'.format(cachesim.utilization))
Intel Advisor Python API 高级主题
Intel Advisor Python API 提供的示例为您编写自己的脚本提供了蓝图。表 2 显示了其中一些高级功能。
以下是我们各种示例的一些亮点。我们正在不断添加示例列表。
生成一个显示所有收集到的数据的组合报告
project = advisor.create_project(project_dir)
生成 HTML 报告
advixe-python to_html.py ./your_project
您可以使用此代码生成一个 Roofline HTML 图表(图 1)。
python roofline.py ./your_project
您必须使用外部 Python 命令运行 `roofline.py` 脚本,而不是 `advixe-python`。它目前仅在 Linux 上运行。它还需要安装额外的库 numpy、pandas 和 matplotlib。使用此代码生成缓存模拟统计信息:
advixe-python cache.py ./your_project
您可以在表 3 中看到我们从缓存模型中获得的结果。
案例研究:向量化比较
在此案例研究中,我们创建了一个 Python 脚本,该脚本可以比较给定循环在以不同编译器选项编译时的向量化情况。
步骤 1:使用不同的优化标志编译代码
首先,使用不同的选项编译应用程序。在此示例中,我们使用 Intel® C++ Compiler(但 Intel Advisor 在二进制级别工作,因此任何编译器都应该有效)。在第一种情况下,我们使用编译器选项 -O0 在没有优化的前提下进行编译。第二种情况使用完全优化 -O3。
icc loops1.cpp -O0 -g -debug inline-debug-info -qopt-report=5 -ipo- -o loops1-no-opt icc loops1.cpp -O3 -g -debug inline-debug-info -qopt-report=5 -ipo- -o loops1
步骤 2:Python 代码
脚本非常简单。首先,从命令行获取一些参数。如果传递了 Intel Advisor 项目,则使用项目中的数据。否则,执行 Intel Advisor 调查运行。调查运行完成后,解码循环的汇编代码,并并排打印这两个循环的指令。我们 Python 代码中的主函数名为 `get_formatted_asm`。此函数能够访问 Intel Advisor 数据库并解码我们循环的汇编代码。它还可以检查汇编代码是否正在使用向量指令,以及循环的执行速度。
import sys import itertools import advisor # second form allows collecting data before analysis # first form just analyses already collected data if len(sys.argv) < 3 or len(sys.argv) > 6: print(''' Usage: advixe-python {} path_to_project_dir loop1 [loop2] Or: advixe-python {} path_to_project_dir loop1 [loop2] executable1 executable2 Where loop1 and loop2 are in the form source:line '''.format(__file__, __file__)) sys.exit(1) project_dir = sys.argv[1] project_dir1 = project_dir + ".1" project_dir2 = project_dir + ".2" loop1 = sys.argv[2] # if we have an odd number of arguments (including script name) then loop2 is the same as loop1 loop2 = sys.argv[3] if len(sys.argv)%2 == 0 else loop1 binary1 = '' binary2 = '' # in the second form two last args are executables to run if 4 < len(sys.argv) < 7: binary1 = sys.argv[-2] binary2 = sys.argv[-1] # try open or create project, run collection if needed: # returns formatted asm listing with vectorized instructions marked with "VEC " for given loop def get_formatted_asm(project_dir, binary, loop): asm = [] try: project = advisor.open_project(project_dir) except: project = advisor.create_project(project_dir) if binary: project.collect(advisor.SURVEY, binary) data = project.load(advisor.SURVEY) for entry in data.bottomup: if loop in entry['function_call_sites_and_loops']: asm += ["{:54.54} ".format(entry['function_call_sites_and_loops']), "{:54.54} ".format("Self time: " + entry['self_time']), " "*54] for instruction in entry.assembly: isVectorized = "VEC" if "VECTORIZED" in instruction['instruction_type'] else "" asm.append("{:4.4}{:50.50} ".format(isVectorized, instruction['asm'])) asm.append("") return asm asm1 = get_formatted_asm(project_dir1, binary1, loop1) asm2 = get_formatted_asm(project_dir2, binary2, loop2) # print alongside asm listings for comparison for (a1,a2) in itertools.izip_longest(asm1, asm2, fillvalue = ' '*40): print("{}{}".format(a1,a2))
步骤 3:运行 Python 脚本
advixe-python compare_asm.py /home/work/projects/loops-compare loops1.cpp:34 /home/work/tests/loops/loops1-no-opt /home/work/tests/loops/loops1
[loop in main at loops1.cpp:34] Self time: 45.5463 Block 1 movl -0xbc(%rbp), %eax movsxd %eax, %rax imul $0x8, %rax, %rax addq -0x88(%rbp), %rax movl -0xac(%rbp), %edx imull -0xac(%rbp), %edx mov $0x1, %ecx addl -0xac(%rbp), %ecx movq %rax, -0x28(%rbp) mov %edx, %eax cdq idiv %ecx cvtsi2sd %eax, %xmm0 movl -0xc0(%rbp), %eax cvtsi2sd %eax, %xmm1 movsdq 0x555(%rip), %xmm2 divsd %xmm2, %xmm1 addsd %xmm1, %xmm0 movq -0x28(%rbp), %rax movsdq (%rax), %xmm1 subsd %xmm0, %xmm1 movl -0xbc(%rbp), %eax movsxd %eax, %rax imul $0x8, %rax, %rax addq -0x88(%rbp), %rax movsdq %xmm1, (%rax) mov $0x1, %eax addl -0xac(%rbp), %eax movl %eax, -0xac(%rbp) movl -0xac(%rbp), %eax cmp $0x64, %eax jl 0x401020 <Block 1>
[loop in main at loops1.cpp:34] Self time: 4.62404 Block 1 VEC movdqa %xmm11, %xmm2 VEC movdqa %xmm11, %xmm0 VEC psrlq $0x20, %xmm2 VEC movdqa %xmm10, %xmm1 VEC pmuludq %xmm2, %xmm2 VEC pmuludq %xmm11, %xmm0 VEC psllq $0x20, %xmm2 VEC pand %xmm13, %xmm0 VEC por %xmm2, %xmm0 callq 0x401770 <__svml_idiv4> Block 2 VEC cvtdq2pd %xmm0, %xmm2 VEC punpckhqdq %xmm0, %xmm0 add $0x4, %r15b VEC cvtdq2pd %xmm0, %xmm3 VEC addpd %xmm14, %xmm2 VEC addpd %xmm14, %xmm3 VEC subpd %xmm2, %xmm12 VEC subpd %xmm3, %xmm8 VEC paddd %xmm15, %xmm11 VEC paddd %xmm15, %xmm10 cmp $0x64, %r15b jb 0x401397 <Block 1>
步骤 4:使用 AVX2 向量化重新编译
现在让我们尝试进一步优化。由于我们的处理器支持 AVX2 指令集,我们将告诉编译器生成 AVX2。(您应该注意,这通常不是编译器默认生成的。)
icc loops1.cpp -O3 -xCORE-AVX2 -g -debug inline-debug-info -qopt-report=5 -ipo- -o loops1-avx2
步骤 5:重新运行比较
advixe-python compare_asm.py /home/work/projects/loops-compare-opt loops1.cpp:34 /home/work/tests/loops/loops1 /home/work/tests/loops/loops1-avx2
[loop in main at loops1.cpp:34] Self time: 4.81401 Block 1 VEC movdqa %xmm11, %xmm2 VEC movdqa %xmm11, %xmm0 VEC psrlq $0x20, %xmm2 VEC movdqa %xmm10, %xmm1 VEC pmuludq %xmm2, %xmm2 VEC pmuludq %xmm11, %xmm0 VEC psllq $0x20, %xmm2 VEC pand %xmm13, %xmm0 VEC por %xmm2, %xmm0 callq 0x401770 <__svml_idiv4> Block 2 VEC cvtdq2pd %xmm0, %xmm2 VEC punpckhqdq %xmm0, %xmm0 add $0x4, %r15b VEC cvtdq2pd %xmm0, %xmm3 cmp $0x60, %r15b VEC addpd %xmm14, %xmm2 jb 0x4015a9 VEC addpd %xmm14, %xmm3 VEC subpd %xmm2, %xmm12 VEC subpd %xmm3, %xmm8 VEC paddd %xmm15, %xmm11 VEC paddd %xmm15, %xmm10 cmp $0x64, %r15b jb 0x401397 <Block 1>
[loop in main at loops1.cpp:34] Self time: 1.97998 Block 1 VEC vpmulld %ymm15, %ymm15, %ymm0 VEC vmovdqa %ymm14, %ymm1 callq 0x4018 f0<__svml_idiv8> Block 2 add $0x8, %r15b VEC vextracti128 $0x1, %ymm0, %xmm2 VEC vcvtdq2pd %xmm0, %ymm3 VEC vpaddd %ymm13, %ymm15, %ymm15 VEC vcvtdq2pd %xmm2, %ymm5 VEC vpaddd %ymm13, %ymm14, %ymm14 VEC vaddpd %ymm3, %ymm10, %ymm4 VEC vaddpd %ymm5, %ymm10, %ymm6 VEC vsubpd %ymm4, %ymm8, %ymm8 VEC vsubpd %ymm6, %ymm9, %ymm9 cmp $0x60, %r15b jb 0x4015a9 <Block 1>
您可以看到,汇编代码现在使用 YMM 寄存器而不是 XMM 寄存器,使向量长度加倍,速度提升 2 倍。
结果
通过优化和使用最新的向量化指令集所带来的收益非常显著。
- 无优化 (-O0):45.148 秒
- 优化 (-O3):4.403 秒
- 优化和 AVX2 (-O3 –AVX2):2.056 秒
最大化系统性能
在现代处理器上,要充分发挥处理器的性能潜力,同时进行软件的向量化和线程化至关重要。Intel Parallel Studio XE 中新的 Intel Advisor Python API 提供了一种强大的方式来生成程序统计信息和报告,这些报告可以帮助您最大限度地提高系统的性能。本文中概述的示例说明了此新接口的强大功能。您可以根据具体需求定制和扩展这些示例。Intel 正在积极收集有关 Intel Advisor Python API 的反馈。如果您尝试过并觉得它很有用,或者想提供反馈,请发送电子邮件至 mvector_advisor@intel.com。