作为 .Net 攻城师,所必需掌握的 .Net Profiling 技术
众所周知,性能问题是所有实用应用在迭代过程中必然要面对的问题。对于此类问题,简单地投入更多硬件资源的做法可能会取得一定效果。但总的来看,此类做法的边际成本是不断上升的。换言之,随着性能需求的上涨,要换取同样的性能提升,仅凭硬件升级所需要的成本会越来越高。故而性能优化是每一位运维/软件开发人员必须掌握的技术。
.Net Profiling
在进行应用性能优化实践时,首先面对的就是热点定位,即确定那些带来巨大资源耗散的代码位置。而在不借助外部工具的前提下,定位资源热点是一件相当困难的事。它需要当事人对于应用实现本身有一个整体的把握,了解应用架构内每一个功能模块代码的路径与细节。与此同时,当事人还要对于应用实现所依赖的第三方功能库的表现有一定的把握。对于那些具备一定规模的应用系统,具备前述素质的工程师的数量屈指可数。而即便是这些百里挑一的优秀人才,其热点预估也不能保证一定是准确的。
Profiling 技术 的提出正是为了解决热点定位而提出的,它以程序的实际运行数据来帮助工程师们来理解应用行为,极大地简化了工程师们的工作。而对于像 .Net 这样比较成熟的技术栈,专家们(如 Bill Chiles 和 赵颉)都推荐工程人员通过实际的 Profiling 数据来定位性能瓶颈。实际操作中,获取 .Net Profiling 数据的功能被实现为一个被称为 Profiler 的 COM 组件实体。下文中,我们将就 Profiler 本身的实现进行一些探讨。
Profiling API
.Net Profiler 在本质上是 CLR 的一个插件,它通过应用 Profiling API 来保持与 CLR 的信息沟通,并以此获取到 .Net 应用的运行时数据。通常来说,Profiler 的实体都表现为一个动态链接库(即 .dll 文件),CLR 在运行时会去加载该库(CLR 加载 Profiler 的详细配置可以参考 这篇文档),并在程序运行的特定阶段向库发送信息并接受库所返回的信息。
需要特别强调的一点是,虽然被称作是 Profiling API,但 CLR 的这套接口能做的可不仅仅是简单地度量应用的运行时间和内存耗散。实践上,profiling API 能够完成诸如代码覆盖、运行时插入等许多高级功能。不过,正如 MSDN Profiling 综述文档 所强调的,Profiling 对于应用本身应该透明。也就是说,在编写应用的时候,开发人员不应该在自己的逻辑中依赖 Profiler 或者被其影响。
对于 .Net 技术栈而言,由于环境本身引入了 application domain, GC, managed exception handling, JIT 等高级特性,Profiling 所展现的就不能仅止于应用运行所消耗的时间或者内存。为了能够真正地表现出运行时行为,Profiling API 中提供了包含这些特性的数据的接口。这就使得 Profiling API 在设计并不是如很多人想得那样直观。
常见的 .Net Profiler 实现多采取如下架构:
.Net Profiler 架构
其中,ICorProfilerCallback 与 ICorProfilerInfo 就是 Profiling API 中最常被应用到的两个。在应用运行时,Profiler DLL 会被加载到应用所在的进程中。通过实现 ICorProfilerCallback 下特定功能的接口,Profiler DLL 会在应用运行时收到相关的动作执行通知。例如,如果在 Profiler DLL 中实现了 ICorProfilerCallback::AssemblyLoadFinished 接口,那么在应用运行中每加载完一个 程序集 时,Profiler DLL 中该接口的实现代码就会被调用。与此类似,Profiler DLL 可以靠实现 ICorProfilerInfo 下的接口来完成对被监测应用状态的获取。
需要补充说明一点,上文所说的 ICorProfilerCallback 接口实际上存在有 ICorProfilerCallback ~ ICorProfilerCallback7 这样7个版本的接口定义。高标号的接口版本向下兼容,但会提供新的功能扩展。不过,更高标号的接口往往也需要有更新版本的 CLR 来支持(如调用 ICorProfilerCallback7 需要在环境中部署 .Net Framework 4.6.1 以上版本),在实际使用时需要多加注意。
目前,Profiling API 可以被任何非托管的 COM 兼容的语言所调用。另外,API 本身的实现非常高效的,不会带来大到足以导致 profiling 失效的额外性能负担。也正因此,基于 Profiling API 完全可以实现一个抽样 profiler(对于 profiling 模式的探讨可参照 这篇文献 的 2.1 章节内容)。
目前 Profiling API 所支持的特性
正如前文所述,Profiler 对于程序行为的描述源自 profiling API 所提供的信息。在目前版本中,凭借 profiling API 能够获取到下列事件的消息通知:
- CLR 的启动与关停
- application domain 的创建与关闭
- 程序集的加载与卸载
- 模块(Module)的加载与卸载
- COM vtable 的创建与销毁
- JIT 编译与 code-pitching 的出发
- 类的加载卸载
- 线程的创建与销毁
- 函数的进入与返回
- 托管代码与非托管代码的执行切换
- 运行时挂起
- 运行时堆内存信息与 GC 活动
随着 .Net 技术的演进,未来的 Profiling API 或许能够提供更多的信息。不过,以下功能点是 Profiling API 不会实现的,请在应用时回避:
- 非托管代码的执行信息
- 运行时修改自身代码的应用的 Profiling(如 AOP)
- 边界检验
- 远程 profiling
- 高可靠性环境下的 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][Ref12] 来指定应用线程模型。同样地,在 Profiler 内部,不要去调用 CoInitialize 以避免与应用代码产生冲突。
调用栈
获取调用栈信息是应用 Profiling 时的一项关键需求。针对这一需求,Profiling API 提供给 Profiler 编写者两种实现方式:栈快照和倒影栈。
栈快照是在特定时刻对特定线程调用栈的一次追踪。需要注意,Profiling API 仅支持对栈上托管函数的追踪。如果 Profiler 需要追踪栈上的非托管函数,则需要自己提供一个栈遍历器出来。读者如对 Profiler 栈快照机制的实现详情感兴趣,可以参照 Divid Broman 的 这篇博客 内容。
频繁使用栈快照会为 CLR 带来过多的额外性能损耗。因此,如果需要频繁进行栈追踪,那么 Profiler 应该通过 FunctionEnter2, FunctionLeave2, FunctionTailCall2 及 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 官方博客
作为 .Net 攻城师,所必需掌握的 .Net Profiling 技术的更多相关文章
- 1年3年5年-我对PHP攻城师有看法
今天早上公车上看微信拉勾的一些岗位推送,挑了几个PHP攻城师看看 15K-20K的 百万级网站架构经验 3年以上开发,至少1年互联网用户产品开团队开发经验 不低于百度T4水平 数据库规划和优化,熟悉常 ...
- java攻城师之路(Android篇)--搭建开发环境、拨打电话、发送短信、布局例子
一.搭建开发环境 1.所需资源 JDK6以上 Eclipse3.6以上 SDK17, 2.3.3 ADT17 2.安装注意事项 不要使用中文路径 如果模拟器默认路径包含中文, 可以设置android_ ...
- java攻城师之路--复习java web之jsp入门_El表达式_JSTL标签库
JSP 技术掌握:JSP语法 + EL + JSTL 为什么sun推出 JSP技术 ? Servlet 生成网页比较复杂,本身不支持HTML语法,html代码需要通过response输出流输出,JSP ...
- 如何成为一名优秀的web前端工程师(前端攻城师)?
程序设计之道无远弗届,御晨风而返.———— 杰佛瑞 · 詹姆士 我所遇到的前端程序员分两种:第一种一直在问:如何学习前端?第二种总说:前端很简单,就那么一点东西. 我从没有听到有人问:如何做一名优秀. ...
- 前端开发攻城师绝对不可忽视的五个HTML5新特性
HTML5已经火了一段时间了,相信作为web相关开发工程师,肯定或多或少的了解和尝试过一些HTML5的特性和编程.还记得以前我们介绍过的HTML5新标签. 作为未来前端开发技术的潮流和风向标,HTML ...
- 【转】如何成为一名优秀的web前端工程师(前端攻城师)?
[转自]http://julying.com/blog/how-to-become-a-good-web-front-end-engineer/ 程序设计之道无远弗届,御晨风而返.———— 杰佛瑞 · ...
- java攻城师之路--复习java web之Cookie_Session
Servlet技术 用来动态生成 网页数据资源Servlet生成HTML 页面数据时,所有内容都是通过 response.getWriter response.getOutputStream 向浏览器 ...
- java攻城师之路--复习java web之request_respone
Servlet技术 两条主线1.HTTP协议 2.Servlet生命周期 init() 方法中参数 ServletConfig 对象使用通过ServletConfig 获得 ServletContex ...
- java攻城师之路--复习java web之servlet
需要掌握的知识点:1.Servlet程序编写 ----- 生命周期2.ServletAPI Request Response 3.Cookie 和 Session Servlet 用来 动态web资源 ...
随机推荐
- Javascript之响应式相册
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- SQL ser 跨实例访问数据库
SqlServer如何跨实例访问数据库 Exec sp_droplinkedsrvlogin LinkName,NullExec sp_dropserver LinkName go EXEC sp_a ...
- [C#]Winform下回车或Tab键自动切换下一个控件焦点
满足用户体验,在数据录入时,能在输入完一个信息后通过回车或Tab键自动的切换到下一个控件(字段). 在界面控件设计时,默认可以通过设置控件的TabIndex来实现.但在布局调整时或者是对输入的内容有选 ...
- Word 2003 出现 向程序发送命令时出现问题 的 解决方案
这种原因出现的问题是word的模板出现问题. 解决方案是重新让word生成Norma.dot文档. 步骤: 1,按住视窗键+R或者开始菜单搜索文件和程序,粘贴 %appdata%\microsoft\ ...
- 限制SSH访问源,禁止4A之外的地址跳转访问
[fuel节点] 在/etc/hosts.allow文件中添加: sshd:10.129.0.1:allow sshd:10.129.0.2:allow sshd:10.129.0.3:allow s ...
- DEDECMS中,channelartlist标签
当前频道的下级栏目的内容列表 dede:channelartlist 标签: {dede:channelartlist row=6} <dl> <dt><a href=' ...
- javascript显示倒计时控制按钮
html: <a><span id="sendAgain" onclick="sendEmail()">2.再次发送激活邮件</s ...
- nginx 配置文件
#基于ip设置 server{ listen 80; server_name 192.168.116.129; location /{ root /usr/etc/ngin/html/ip; inde ...
- android eclipse集成环境
Android开发工具(ADT)是一个插件为Eclipse IDE,它的目的是给你一个强大的,集成的环境来构建Android应用程序. ADT扩展了Eclipse的功能使用Android SDK工具, ...
- linux笔记1
在root下创建用户 1.useradd abc //添加一个新用户 2. cat /etc/passwd //查看新用户是否存在 3.passwd abc 输入密码 (123456) ...