本文旨在强调编译器优化的功效,重点关注因受欢迎和广泛使用而闻名的英特尔 C++ 编译器。
亮点:什么是编译器优化? | -开|架构目标 |过程间优化 | -fno-别名 |编译器优化报告
任何人编译器总会审理一种的操作步骤将中高级源编号准换为发瘟匣设备编号。这样的牵涉词法浅析、日语语法浅析、语义浅析、在期间编号导出(或 IR)、优化网络和编号导出。
在优化阶段,编译器会仔细寻找转换程序的方法,旨在获得语义等效的输出,从而利用更少的资源或更快地执行。此过程中采用的技术包括但不限于常量折叠、循环优化、函数内联和死代码消除。
开发人员可以在编译过程中指定一组编译器标志,这种做法对于使用 GCC 的“ -g”或“-pg”等选项来调试和分析信息的人来说很熟悉。接下来,我们将讨论在使用英特尔 C++ 编译器编译应用程序时可以使用的类似编译器标志。这些可能会帮助您提高代码的效率和性能。
u(x,y,t) 是时 t 点 (x,y) 的水温。
我本质上上的个 C++ 代码,在可变性面积深浅不一的网格(我称作为粪便率)上连接雅可比迭代的。几乎上,网格面积深浅不一为 500 是因为着推导面积深浅不一为 500x500 的矩阵的特征值,依此种推。
/* * One Jacobi iteration step */ void jacobi(double *u, double *unew, unsigned sizex, unsigned sizey) { int i, j; for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { unew[i * sizex + j] = 0.25 * (u[i * sizex + (j - 1)] + // left u[i * sizex + (j + 1)] + // right u[(i - 1) * sizex + j] + // top u[(i + 1) * sizex + j]); // bottom } } for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { u[i * sizex + j] = unew[i * sizex + j]; } } }
MFLOP/s 指代“每秒100万次浮点运算”。它是一种种检测企业,使用于批量估算机或工作器在浮点运算方便的效能。浮点运算牵涉以浮点文件类型带表的小数或实数的数学知识估算。
注 1:为了提供稳定的结果,我针对每个分辨率运行可执行文件 5 次,并取 MFLOP/s 值的平均值。
注 2:需要注意的是,Intel C++ 编译器的默认优化是 -O2。因此,在编译源代码时指定-O0很重要。
当人们开始进行编译器优化时,这些是一些最常用的编译器标志。理想情况下, Ofast > O3 > O2 > O1 > O0的性能。然而,这并不一定会发生。这些选项的关键点如下:
-O1:
-氧气:
-O3:
-奥法斯特:
很特别,所有的此类优化方案都比我们都的根本编号(配有“-O0”)快得多。下达正常运行期限比根本现象低 2-3 倍。 MFLOP/s 如何样?
总体而言,“-O3”表现最好,尽管只有一点点。
“- Ofast ”(“ -no-prec-div -fp-model fast=2 ”)使用的额外标志不会提供任何额外的加速。
答案在于战略编译器标志。尝试“ -xHost ”,更准确地说,“ -xCORE-AVX512 ”等选项可以让我们充分利用机器功能的潜力,并进行定制优化以获得最佳性能。
-x主机:
-xCORE-AVX512:
目标:明确指示编译器生成利用英特尔高级矢量扩展 512 (AVX-512) 指令集的代码。
主要特点: AVX-512 是一种先进的 SIMD(单指令、多数据)指令集,与 AVX2 等以前的版本相比,它提供更宽的矢量寄存器和附加操作。启用此标志允许编译器利用这些高级功能来优化性能。
注意事项:可移植性又是这里的罪魁祸首。使用 AVX-512 指令生成的二进制文件可能无法在不支持该指令集的处理器上以最佳方式运行。它们可能根本不起作用!
默认情况下,“ -xCORE-AVX512 ”假定程序不太可能从 zmm 寄存器的使用中受益。除非保证性能增益,否则编译器会避免使用 zmm 寄存器。
如果打算不受限制地使用 zmm 寄存器,“ ”可以设置为高。这也是我们要做的事情。
呜呼!
需要目光的是,人们在无任意本质性手动式干涉的情况报告下就具有了等最后——只需在应用领域系统软件编译步骤中并到一下编译器圆形标志如要。
注意:如果您的硬件不支持 AVX-512,请不要担心。英特尔 C++ 编译器支持 AVX、AVX-2 甚至 SSE 的优化。该包含您需要了解的一切!
IPO 都是个多部骤流程,侧重点关注度子编译程序内不一样的功能模块或子编译程序当中的等交互。 IPO 就可以还涉及许许多多不一样的品类的推广,还涉及前向代替、接间赋值转型和内联。
-首次公开募股:
目标:启用过程间优化,允许编译器在编译期间分析和优化整个程序,而不仅仅是单个源文件。
主要特点:-整个程序优化:“ -ipo ”对所有源文件进行分析和优化,考虑整个程序中函数和过程之间的交互。- 跨函数和跨模块优化:该标志有利于内联函数、同步优化以及跨不同程序部分的数据流分析。
注意事项:它需要单独的链接步骤。使用“ -ipo ”编译后,需要特定的链接步骤来生成最终的可执行文件。编译器在链接期间根据整个程序视图执行额外的优化。
-ip:
目标:启用过程间分析传播,允许编译器执行一些过程间优化,而不需要单独的链接步骤。
主要功能:-分析和传播:“ -ip ”使编译器能够在编译期间跨不同函数和模块执行研究和数据传播。但是,它不会执行需要完整程序视图的所有优化。- 更快的编译:与“ -ipo ”不同,“ -ip ”不需要单独的链接步骤,从而加快编译时间。当快速反馈至关重要时,这在开发过程中会很有用。
注意事项:仅发生一些有限的过程间优化,包括函数内联。
-ipo 基本上作为更诸多的整个过程间优化网络的功能,所以它涵盖直接的链接转换步驟,但价格是编译的时间更长。 [ ] -ip 是一种种最快的速度的代用情况报告,是可以执行命令几个流程间提高,不用再单独的的图片链接布骤,使其时候開發和检查过程。[ ]
由于我们只讨论性能和不同的优化、编译时间或可执行文件的大小不是我们关心的,因此我们将重点关注“ -ipo ”。
/* * One Jacobi iteration step */ void jacobi(double *u, double *unew, unsigned sizex, unsigned sizey) { int i, j; for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { unew[i * sizex + j] = 0.25 * (u[i * sizex + (j - 1)] + // left u[i * sizex + (j + 1)] + // right u[(i - 1) * sizex + j] + // top u[(i + 1) * sizex + j]); // bottom } } for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { u[i * sizex + j] = unew[i * sizex + j]; } } }
jacobi() 函数采用几个指针作为参数,然后在嵌套的 for 循环中执行某些操作。当任何编译器在源文件中看到这个函数时,都必须非常小心。
使用u计算unew 的表达式涉及 4 个相邻u值的平均值。如果u和unew都指向同一个位置怎么办?这将成为别名指针的经典问题[ ]。
中国现代编译器十分的聪慧,因为确定安全性高,这些假如有可能会发生別称。关于如此一来的景象,两人制止了每有可能会导致编码语义和打出的seo。
在我们的例子中,我们知道u和unew是不同的内存位置,并且用于存储不同的值。因此,我们可以轻松地让编译器知道这里不会有任何别名。
有两种方法。首先是C 语言的“ ”关键字。但这需要更改代码。我们暂时不想这样。
有什么简单的吗?让我们尝试一下“ -fno-alias ”。
-fno-别名:
目标:指示编译器不要假设程序中存在别名。
主要特点:假设没有别名,编译器可以更自由地优化代码,从而潜在地提高性能。
注意事项:开发人员在使用此标志时必须小心,因为如果出现任何不必要的别名,程序可能会给出意外的输出。
好的,当前你们有食物了!!!
仔细检查汇编代码(虽然这里没有共享)和生成的编译优化报告(见下文)揭示了编译器对和的精明应用。这些转换有助于实现高度优化的性能,展示编译器指令对代码效率的重大影响。
英特尔 C++ 编译器提供了一项有价值的功能,允许用户生成优化报告,总结为优化目的所做的所有调整 [ ]。这份综合报告以 YAML 文件格式保存,提供编译器在代码中应用的优化的详细列表。详细说明请参见官方文档“ ”。
同样,英特尔 C++ 编译器(以及所有流行的编译器)也支持 pragma 指令,这是非常好的功能。值得在上检查一些编译指示,如ivdep、parallel、simd、vector等。