五十五、PGO优化---RednaxelaFX

2024-05-10 21:32

本文主要是介绍五十五、PGO优化---RednaxelaFX,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在微博上看到有人在推广这篇介绍Visual C++与RyuJIT的文章:

每个程序员都应当知道的编译器优化知识

(原文:Compilers - What Every Programmer Should Know About Compiler Optimizations, MSDN Magazine 2015-02)

这篇文章总体来说写得还不错。不过在描述RyuJIT的功能时略微夸大了一些:

What’s the difference between RyuJIT and Visual C++ in terms of optimization capabilities? Because it does its work at run time, RyuJIT can perform optimizations that Visual C++ can’t.  For example, at run time, RyuJIT might be able to determine that the condition of an if statement is never true in this particular run of the application and, therefore, it can be optimized away.

这说的主要是一种PGO优化(profile-guided optimization)的效果(注1)。虽然“理论上说”这是可能的,但CLR使用JIT编译器的方式使得RyuJIT实际上无法在JIT编译时做到这种优化。就像评论里

@空明流转

大大说的:

我是觉得,“对JIT编译器抱有过高期望”一直都是非编译器行业的一种长期偏见。

我完全同意。所以才要来解剖一下。

想起之前回答过的一个问题:HotSpot是较新的Java虚拟机技术,用来代替JIT技术,那么HotSpot和JIT是共存的吗? - RednaxelaFX 的回答

本文想写的东西有一半在上面的链接里,请先读完它在继续向后阅读后文。重点是:

  • 动态编译(dynamic compilation)指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。
  • JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。
  • JIT编译一词后来被泛化,时常与动态编译等价;但要注意宽泛与狭义的JIT编译所指的区别。
  • 自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种形式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化,可以很自然的融入PGO优化。这个“某种形式”可以称为“baseline execution“,可以由解释器或简单的JIT编译器承担。


 

CLR,JIT编译与PGO

 

针对CLR / RyuJIT的场景稍微展开一下。

CLR的执行引擎的模型是“纯编译的单层JIT编译器“。所有被执行的有MSIL方法体的方法,要么被NGen事先编译成机器码了(PreJIT),要么在方法第一次被调用前进行JIT编译到机器码;总之就是MSIL要编译成机器码之后才被执行。

在正常执行时,每个有MSIL方法体的方法只有一次被编译的机会;一旦被编译过了就雷打不动了。对单次执行来说,CLR这种“单层JIT编译、只编译一次“的模型意味着代码被编译的时候还一次都没被执行过,也就无从借助运行时收集的profile信息来做有针对性的优化。

与之相对,自适应动态编译由于可以等程序执行了一段时间之后才做编译,所以有充分时间收集profile并利用profile来优化,经典例子就是收集分支跳转的次数,看taken分支还是not-taken分支的次数多。这样才可以做到本文开头引用的那段文字所描述的优化。

从.NET 4.5开始,CLR支持一个新功能叫做ReJIT,用于支持managed profiler通过动态插桩(instrument)来收集性能数据(profile)。动态插桩意味着修改某个已有方法的MSIL方法体,所以在修改过后需要重新JIT编译才可以执行,因而得名ReJIT。注意ReJIT功能自身并不用于收集profile;收集profile的是CLR外部的managed profiler。所以收集到的profile也无法供给CLR的JIT编译器使用。

这与CLR正常执行时的模型其实仍然一致:每个MSIL方法体仍然只有一次被编译的机会;所谓“重新JIT编译“编译的是新的MSIL方法体。

.NET 4.5还引入了“Multicore JIT”:在第一次运行程序时,用recording mode记录下哪些方法被JIT编译过,写入profile文件;以后执行可以用playback mode在后台线程先JIT编译这个列表里的方法,这样等到应用真的第一次调用某个方法时它可能已经被JIT编译好了。这是JIT编译的一种特殊做法,虽然没有脱离JIT编译的范畴但增加了一个自由度。

当前这个Multicore JIT的设计只针对程序启动速度优化,它只观察和记录JIT编译活动,生成的profile粒度太粗,仅是“被JIT编译过的方法的列表“,而不包含任何细粒度的profiling信息(例如说条件分支、虚方法的被调用对象的实际类型等)。所以仍然做不到本文开头所说的PGO优化。

但基于这个架构,后续开发完全可以添加更多功能,例如在recording mode中让JIT编译器生成收集profile的代码,收集细粒度profile(例如类型信息、分支跳转情况等),并将收集到的profile写入文件;后续执行就可以根据细粒度profile来做高度优化的编译。

其实.NET 4.5已经有了类似的PGO功能,但不用于JIT编译场景而用于NGen(AOT编译)场景。这就是MPGO(managed profile-guided optimization)。当使用MPGO来NGen时,要分三步来生成最终的NGen二进制映像:

  1. 使用MPGO,NGen生成带有收集profile功能的代码;
  2. 使用(1)生成的代码运行若干次该程序的“典型场景”,以便收集profile。这叫做training run;
  3. 结合(2)收集到的profile,再次运行MPGO来完成最终的NGen。

这个过程跟一般的native PGO优化一样,都是要运行多次,依赖training run收集到的profile来引导后续运行的优化。在这种模式下运行的RyuJIT就可以做到类似本文开头所说的优化——但Visual C++的编译器同样支持PGO,同样可以做这种优化(而且可能做得更好)。相信未来的.NET Native在逐渐成熟起来之后也会考虑支持PGO。

自适应动态编译的思路就是把这种多次运行才能得到的好处压缩在一次运行中完成:执行引擎自动在开始的时候生成收集profile的代码,然后自动收集profile,最后自动利用profile来做优化编译。这样不但用起来方便,而且可以保证收集到的profile能够反映本次运行的特征。Native PGO的多次运行模型非常依赖于training run的代表性,如果其特征与后来实际运行的特征不匹配,那PGO“优化”反而会带来性能损失。

HotSpot VM是一个典型的自适应动态编译系统,使用解释器或Client Compiler(C1)来实现初始执行和profile的收集,然后把profile信息交给Server Compiler(C2)做优化编译。

对HotSpot VM的执行模型感兴趣的同学可以参考同事Doug Hawkins做的演讲来了解更多细节:JVM Mechanics by Douglas Hawkins as Presented at Silicon Valley Java User Group by Azul Systems on Vimeo


 

传统PGO与自适应动态编译的结合

 

有没有办法结合传统的PGO与自适应动态编译呢?Azul Systems的Zing VM所实现的ReadyNow!功能是一个思路。

Zing VM基于HotSpot VM开发,与HotSpot VM的执行模式相似,都是解释器+C1+C2的多层混合模式执行引擎,使用了自适应动态编译。

能在程序运行一次的过程中就自动进行PGO当然是方便,但收集profile时总有额外开销,不一定适用于所有场景。特别是,收集profile通常发生在程序启动阶段,也就是说启动时会比较慢;如果特别在意程序启动的速度的话,这种做法就不合适了。

ReadyNow!的思路是让传统PGO与自适应动态编译结合起来:一个程序可以先跑些training run把细粒度profile信息记录下来,后续执行的时候可以跳过原本收集profile的阶段,直接利用之前记录的profile信息来做优化编译。这样就减少了程序启动时收集profile的开销,让程序快速达到稳定的高性能状态。

在Zing VM中,ReadyNow!不但能通过profile信息来指导优化,还可以指导不做某些过于激进的优化,减少因过度优化而导致的“去优化”(deoptimization)。这样也有利于程序快速达到稳定的性能水平,而不必在过度优化—去优化-再优化-再去优化-⋯的震荡多次后才达到稳定。



==========================================================
 

* 注1:也有一些场景RyuJIT确实可以做到文中所说优化,例如在JIT编译时把已经初始化好的readonly变量看作编译时常量来做常量传播和折叠的优化。用代码举例的话,可以是:

static class MyConfig {static readonly bool _debug = ConfigFile.GetProperty("debug");public static bool IsDebug { get { return _debug; } }
}// ...
if (MyConfig.IsDebug) {Logger.log("...");
}

此例中 if (MyConfig.IsDebug) 可以先内联IsDebug属性得知它就是_debug,然后发现它是readonly静态变量,直接用它的值来做优化;假如其值是false,那代码就变成 if (false) ,整个if块就可以被优化消除掉了。

具体逻辑从CoreCLR源码可以看到,在Compiler::impImportBlockCode(BasicBlock * block):

            case CORINFO_FIELD_STATIC_ADDRESS:// Replace static read-only fields with constant if possibleif ((aflags & CORINFO_ACCESS_GET) && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_FINAL) &&!(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) &&(varTypeIsIntegral(lclTyp) || varTypeIsFloating(lclTyp))){     CorInfoInitClassResult initClassResult = info.compCompHnd->initClass(resolvedToken.hField, info.compMethodHnd,impTokenLookupContextHandle);if (initClassResult & CORINFO_INITCLASS_INITIALIZED){     void **  pFldAddr = NULL; void * fldAddr = info.compCompHnd->getFieldAddress(resolvedToken.hField, (void**) &pFldAddr);// We should always be able to access this static's address directlyassert(pFldAddr == NULL);op1 = impImportStaticReadOnlyField(fldAddr, lclTyp);goto FIELD_DONE;}     }

不过这是比较特殊的场景,而PGO能覆盖的是更一般的场景。

这篇关于五十五、PGO优化---RednaxelaFX的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977611

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

构建高性能WEB之HTTP首部优化

0x00 前言 在讨论浏览器优化之前,首先我们先分析下从客户端发起一个HTTP请求到用户接收到响应之间,都发生了什么?知己知彼,才能百战不殆。这也是作为一个WEB开发者,为什么一定要深入学习TCP/IP等网络知识。 0x01 到底发生什么了? 当用户发起一个HTTP请求时,首先客户端将与服务端之间建立TCP连接,成功建立连接后,服务端将对请求进行处理,并对客户端做出响应,响应内容一般包括响应

DAY16:什么是慢查询,导致的原因,优化方法 | undo log、redo log、binlog的用处 | MySQL有哪些锁

目录 什么是慢查询,导致的原因,优化方法 undo log、redo log、binlog的用处  MySQL有哪些锁   什么是慢查询,导致的原因,优化方法 数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。 导致的原因: 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询,可能导致执行时间较长。查询数据量大:当查询的数据量庞大时,即使查询本身并不复杂,也可能导致

MySQL 数据优化

MySQL 数据优化的指南 MySQL 数据库优化是一个复杂且重要的过程,它直接影响到系统的性能、可靠性和可扩展性。在处理大量数据或高并发请求时,数据库的优化尤为关键。通过合理的数据库设计、索引使用、查询优化和硬件调优,可以大幅提高 MySQL 的运行效率。本文将从几个主要方面详细介绍 MySQL 的优化技巧,帮助你在实际应用中提升数据库性能。 一、数据库设计优化 1. 数据库的规范化与反规

C++编程:ZeroMQ进程间(订阅-发布)通信配置优化

文章目录 0. 概述1. 发布者同步发送(pub)与订阅者异步接收(sub)示例代码可能的副作用: 2. 适度增加缓存和队列示例代码副作用: 3. 动态的IPC通道管理示例代码副作用: 4. 接收消息的超时设置示例代码副作用: 5. 增加I/O线程数量示例代码副作用: 6. 异步消息发送(使用`dontwait`标志)示例代码副作用: 7. 其他可以考虑的优化项7.1 立即发送(ZMQ_IM