做为 .Net 攻城师,所必需掌握的 .Net Profiling 技术

众所周知,性能问题是全部实用应用在迭代过程当中必然要面对的问题。对于此类问题,简单地投入更多硬件资源的作法可能会取得必定效果。但总的来看,此类作法的边际成本是不断上升的。换言之,随着性能需求的上涨,要换取一样的性能提高,仅凭硬件升级所须要的成本会愈来愈高。故而性能优化是每一位运维/软件开发人员必须掌握的技术。html

.Net Profiling

在进行应用性能优化实践时,首先面对的就是热点定位,即肯定那些带来巨大资源耗散的代码位置。而在不借助外部工具的前提下,定位资源热点是一件至关困难的事。它须要当事人对于应用实现自己有一个总体的把握,了解应用架构内每个功能模块代码的路径与细节。与此同时,当事人还要对于应用实现所依赖的第三方功能库的表现有必定的把握。对于那些具有必定规模的应用系统,具有前述素质的工程师的数量屈指可数。而即使是这些凤毛麟角的优秀人才,其热点预估也不能保证必定是准确的。浏览器

Profiling 技术) 的提出正是为了解决热点定位而提出的,它以程序的实际运行数据来帮助工程师们来理解应用行为,极大地简化了工程师们的工做。而对于像 .Net 这样比较成熟的技术栈,专家们(如 Bill Chiles赵颉)都推荐工程人员经过实际的 Profiling 数据来定位性能瓶颈。实际操做中,获取 .Net Profiling 数据的功能被实现为一个被称为 Profiler 的 COM 组件实体。下文中,咱们将就 Profiler 自己的实现进行一些探讨。安全

Profiling API

.Net Profiler 在本质上是 CLR.aspx) 的一个插件,它经过应用 Profiling API.aspx) 来保持与 CLR 的信息沟通,并以此获取到 .Net 应用的运行时数据。一般来讲,Profiler 的实体都表现为一个动态连接库(即 .dll 文件),CLR 在运行时会去加载该库(CLR 加载 Profiler 的详细配置能够参考 这篇文档.aspx)),并在程序运行的特定阶段向库发送信息并接受库所返回的信息。性能优化

须要特别强调的一点是,虽然被称做是 Profiling API,但 CLR 的这套接口能作的可不只仅是简单地度量应用的运行时间和内存耗散。实践上,profiling API 可以完成诸如代码覆盖、运行时插入等许多高级功能。不过,正如 MSDN Profiling 综述文档.aspx) 所强调的,Profiling 对于应用自己应该透明。也就是说,在编写应用的时候,开发人员不该该在本身的逻辑中依赖 Profiler 或者被其影响。数据结构

对于 .Net 技术栈而言,因为环境自己引入了 application domain.aspx), GC, managed exception handling, JIT 等高级特性,Profiling 所展示的就不能仅止于应用运行所消耗的时间或者内存。为了可以真正地表现出运行时行为,Profiling API 中提供了包含这些特性的数据的接口。这就使得 Profiling API 在设计并非如不少人想得那样直观。多线程

常见的 .Net Profiler 实现多采起以下架构:

.Net Profiler 架构架构

其中,ICorProfilerCallback.aspx) 与 ICorProfilerInfo.aspx) 就是 Profiling API 中最常被应用到的两个。在应用运行时,Profiler DLL 会被加载到应用所在的进程中。经过实现 ICorProfilerCallback 下特定功能的接口,Profiler DLL 会在应用运行时收到相关的动做执行通知。例如,若是在 Profiler DLL 中实现了 ICorProfilerCallback::AssemblyLoadFinished 接口,那么在应用运行中每加载完一个 程序集.aspx) 时,Profiler DLL 中该接口的实现代码就会被调用。与此相似,Profiler DLL 能够靠实现 ICorProfilerInfo 下的接口来完成对被监测应用状态的获取。并发

须要补充说明一点,上文所说的 ICorProfilerCallback 接口实际上存在有 ICorProfilerCallback ~ ICorProfilerCallback7 这样7个版本的接口定义。高标号的接口版本向下兼容,但会提供新的功能扩展。不过,更高标号的接口每每也须要有更新版本的 CLR 来支持(如调用 ICorProfilerCallback7 须要在环境中部署 .Net Framework 4.6.1 以上版本),在实际使用时须要多加注意。app

目前,Profiling API 能够被任何非托管的 COM 兼容的语言所调用。另外,API 自己的实现很是高效的,不会带来大到足以致使 profiling 失效的额外性能负担。也正所以,基于 Profiling API 彻底能够实现一个抽样 profiler(对于 profiling 模式的探讨可参照 这篇文献 的 2.1 章节内容)。运维

目前 Profiling API 所支持的特性

正如前文所述,Profiler 对于程序行为的描述源自 profiling API 所提供的信息。在目前版本中,凭借 profiling API 可以获取到下列事件的消息通知:

  1. CLR 的启动与关停

  2. application domain 的建立与关闭

  3. 程序集的加载与卸载

  4. 模块(Module)的加载与卸载

  5. COM vtable 的建立与销毁

  6. JIT 编译与 code-pitching 的出发

  7. 类的加载卸载

  8. 线程的建立与销毁

  9. 函数的进入与返回

  10. 托管代码与非托管代码的执行切换

  11. 运行时挂起

  12. 运行时堆内存信息与 GC 活动

随着 .Net 技术的演进,将来的 Profiling API 或许可以提供更多的信息。不过,如下功能点是 Profiling API 不会实现的,请在应用时回避:

  1. 非托管代码的执行信息

  2. 运行时修改自身代码的应用的 Profiling(如 AOP)

  3. 边界检验

  4. 远程 profiling

  5. 高可靠性环境下的 profiling

线程相关

对于加载了 Profiler DLL 的进程而言,其在建立新线程时,新线程自己也会产生 ICorProfilerCallback 接口下定义的各类事件通知。这一过程当中,Profiler 没必要去显式地指定一个 ThreadID 以使得 Profiling API 生效。一样的,Profiler 彻底能够简单地在代码中使用 thread-local 的存储方式,用不着费心地去进行存储位置的全局重定向。

固然,仍是有一些要点须要咱们在并发背景下留意。例如,Profiling API 自己并不能保障数据结构的线程安全,所以咱们须要在可能产生并行访问的地方给 Profiler 代码冲突区加锁以保证 Profiler 的行为符合预期。一样,对于多线程的应用场景来讲,Profiler 不该该假设 ICorProfilerCallback 的各个接口存在必定的前后顺序。举例来讲,一个有两个一样线程的程序在运行时可能会先产生一个 FunctionEnter 而后才产生 ICorProfilerCallback::JITCompilationFinished。

还有一个线程相关的问题是来自于 COM 接口的。上文中咱们说过 Profiler 事实上是实现为一个 COM 组件的,但其实 CLR 在运行时并不会去初始化 COM。这是为了不在应用代码指定线程模型前,CLR 调用 CoInitialize 来指定应用线程模型。一样地,在 Profiler 内部,不要去调用 CoInitialize 以免与应用代码产生冲突。

调用栈

获取调用栈信息是应用 Profiling 时的一项关键需求。针对这一需求,Profiling API 提供给 Profiler 编写者两种实现方式:栈快照和倒影栈。

栈快照是在特定时刻对特定线程调用栈的一次追踪。须要注意,Profiling API 仅支持对栈上托管函数的追踪。若是 Profiler 须要追踪栈上的非托管函数,则须要本身提供一个栈遍历器出来。读者如对 Profiler 栈快照机制的实现详情感兴趣,能够参照 Divid Broman 的 这篇博客 内容。

频繁使用栈快照会为 CLR 带来过多的额外性能损耗。所以,若是须要频繁进行栈追踪,那么 Profiler 应该经过 FunctionEnter2.aspx), FunctionLeave2.aspx), FunctionTailCall2.aspx) 及 ICorProfilerCallback::Exception* 等一系列接口构建出当前应用调用栈的一个倒影。如此,Profiler 便可低消耗地进行栈追踪操做。

其余须要留意之处

前文强调过,Profiler 是一个非托管的 DLL 库,会在应用运行时被加载到 CLR 中并与应用处于同一进程空间下。如此,Profiler DLL 实质上是不受托管代码的访问控制的。其运行惟一的限制就是运行 Profiler 的 OS 用户必须拥有足够权限。所以,对于要部署 Profiler 的技术人员来讲,必需要明白这其中可能的风险,提前进行准备。例如能够把 Profiler DLL 加到访问控制列表(ACL)中以避免恶意用户对其加以利用。

还有,Profiler DLL 做为 CLR 的一个插件,其运行错误可能会引发 CLR 自己的崩溃,在实施时必定要足够当心。而对于那些运行在栈内存空间紧张的环境下的 Profiler,要警戒由于 ICorProfilerCallback 致使栈溢出而引发的应用崩溃。在这种资源受限的环境中,要尽量地减小 Profiler 自身的资源耗散。尽力避免本来可以运行的应用由于 Profiler 而致使没法运行的状况。

结语

本文简述了 .Net Profiling 技术的整体状况,并就其中的一些重要技术点进行了阐述。但愿能帮助读者初步理解 .Net Profiling 技术。后续,咱们将具体到代码实施层面,就 .Net Profiling 的实现进行详细讨论

OneAPM 助您轻松锁定 .NET 应用性能瓶颈,经过强大的 Trace 记录逐层分析,直至锁定行级问题代码。以用户角度展现系统响应速度,以地域和浏览器维度统计用户使用状况。想阅读更多技术文章,请访问 OneAPM 官方博客
本文转自 OneAPM 官方博客

相关文章
相关标签/搜索