(译者:这篇文章作者是一位美国的MVP,这是他的系列文章"Under the cover"的第一篇,文章的本意从最底层的角度来优化代码的性能,并作为阅读作者其他文章的技术基础,这种通过这样的做法虽然初看起来有些过分,但是对读者了解.Net许多底层运作是十分有益的)

我们从使用visual studio进行非托管代码调试的基础开始,以便大家可以更容易的学习今后的例子,并让这篇文章作为我以后文章的基础,虽然我也使用windbg,但visual studio已经成为了一个功能强大的调试工具,对于简单的代码优化问题反而更容易使用

当我们需要校调对性能要求很高的代码时,查看IL通常不是最好的做法,因为JIT优化器会默默的优化我们的代码,使用reflector或者ildasm你能很快发现release和debug模式下产生的IL代码几乎完全相同,那么是什么让release模式的代码运行起来如此迅速呢?这就是JIT优化的结果,通过查看managed代码(IL代码),我们没有办法看到这些优化,所以我们将通过native code(本地代码)来查找蛛丝马迹。

必须说明我不提倡大家经常这样做,我不赞成过早的进行优化,你必须使你的代码先工作起来,你必须清楚的知道哪些代码是不值得优化的,当你的代码完成后再来找那些需要提速的地方,当你发现有的地方10% 的代码却使用了70%的时间的时候,再回过头去优化那10%的代码.同时你总是应该把判断的依据建立在对速度的实际测量上,而非仅仅是阅读代码,最后,其实数据结构的选择比底层的优化重要的多

当然话又说回来,了解隐藏在.Net底层的秘密是非常有趣的,那就让我们开始设置visualstudio,并动手试验一个简单的例子

首先我们需要一些试验代码

static void Main(string[] args) {

for (int i = 0; i < 10; i++) {

Console.WriteLine("Hello World!");

}

}

为了打开非托管代码调试,我们需要对visual studio进行设置.打开项目的属性并进入Debug Tab,选择该页上的“Enable unmanaged code debugging”复选框

(注意,这个选项只对当前使用的配置有效,因此我们应该为我们使用的所有配置设置这个选项.)在循环的开始处插入一个断点,并运行程序,你将会像往常一样击中一个断点。这时你的屏幕应该看起来如图二(译者:原文缺图)如果你没有stack窗口,可以通过menu -> windows -> call stack (或者  ctrl + d  c)将其呼出,打开call stack后,我们就可以通过右击鼠标,选择go to disassembly进入下面的代码

static void Main(string[] args) { 00000000 push ebp 00000001 mov ebp,esp 00000003 push edi 00000004 push esi 00000005 push ebx 00000006 sub esp,38h 00000009 xor eax,eax 0000000b mov dword ptr [ebp-10h],eax 0000000e xor eax,eax 00000010 mov dword ptr [ebp-1Ch],eax 00000013 mov dword ptr [ebp-3Ch],ecx 00000016 cmp dword ptr ds:[00912DC8h],0 0000001d je 00000024 0000001f call 792B228E 00000024 xor esi,esi 00000026 xor edi,edi 00000028 nop for (int i = 0; i < 10; i++) { 00000029 xor esi,esi 0000002b nop 0000002c jmp 0000003D 0000002e nop Console.WriteLine("Hello World!"); 0000002f mov ecx,dword ptr ds:[022B303Ch] 00000035 call 785D9074 0000003a nop } 0000003b nop for (int i = 0; i < 10; i++) { 0000003c inc esi 0000003d cmp esi,0Ah 00000040 setl al 00000043 movzx eax,al 00000046 mov edi,eax 00000048 test edi,edi 0000004a jne 0000002E } 0000004c nop 0000004d lea esp,[ebp-0Ch] 00000050 pop ebx 00000051 pop esi 00000052 pop edi 00000053 pop

我们正在查看的就是JIT为我们的代码产生的native code(本地代码),我们可以看到简单的循环在native code层次上怎么运行的,如果你从来没有研究过native code,这些本来很普通的代码可能看起来相当的奇怪,让我们来自己看看这里发生了什么

00000029  xor         esi,esi

0000002b  nop

0000002c  jmp         0000003D

上面代码初始化我们在ESI中的计数器,ESI是一个索引寄存器,可以用来索引数组,你可以看到这里用了一个很古老的"把戏"来把计数器清0,代码没有使用把0值放入寄存器,而是让寄存器自己异或(xor)自己来达到清0的目的,接下来的一行Nop,意思是"没有操作",而他们的作用就和他们的名字一样,什么也不做,代码接下来立即跳转到3D.有时候像这样的跳转使得我们的代码不是自上而下的运行(就象许多高级语言比如c,vb,c#里面一样),如果跟着这个跳转进入这个循环的另外一个部分,就可以继续分析我们的代码

0000003c  inc         esi

0000003c后面第一个指令把ESI中的计数器加一(通过register窗口或者组合键ctrl+D R  你可以看到它的值),在第一次循环时代码会跳过这行,因为上面的跳转指令直接指向了0000003D

0000003d  cmp         esi,0Ah

00000040  setl        al

00000043  movzx       eax,al

00000046  mov         edi,eax

00000048  test        edi,edi

0000004a  jne         0000002E

从0000003D开始到4a,代表于循环停止值的实际比较和跳转如果我们没有达到这个值(i<10),最后一行会跳转到2e(译者注:原著这里为4a,是个笔误)继续这个循环,也就是循环体开始的地方
0000002f  mov         ecx,dword ptr ds:[022B303Ch]

00000035  call        785D9074

上面的第一条行将会把字符串从从内存装在到ECX 寄存器 (这是一个通用寄存器), 一般ECX总是用作把第一个参数传给方法,在实例的方法中,ECX将总是包含this,紧接着是包含第二个参数的EDX,然后是一系列的push,用于把其他参数入栈

下一条语句执行实际的调用。我们待会再来探讨怎么去查找所调用的方法,但是现在我们可以从VisualStudio给出的源代码看到,这毫无疑问就是 Console.WriteLine ,代码接着执行索引的自增,并返回来继续执行loop循环内部的代码

然而,我们的微不足道的例子中已经产生产生了明显的浪费。下面是一个例子
00000009  xor         eax,eax

0000000b  mov         dword ptr [ebp-10h],eax

0000000e  xor         eax,eax

00000010  mov         dword ptr [ebp-1Ch],eax

我们在一行里两次对EAX置0,这时因为我们正运行在debug模式下,调试模式下是不进行优化的,换句话说,这段代码只是被JIT执行,但是却没有允许JIT作任何智能优化

下面让我们来看看经过优化的代码:

这里有一些关于查看优化代码的问题
1)是调试器默认关闭了JIT的优化(我自己就曾经在大半夜花了很长时间才意识到自己一直在看没有被优化的代码)
 2)是必须处理"Just My Code"选项对优化代码的影响

我最初在Vance Morrison的帖子上看到了解决这个问题的办法(谢谢 Vance,我已经被整个问题困扰了很长一段时间,并最终使用直接查看没有源码的原始assemble的方式).

要搞定这个问题,清跟着以下的步骤作

1) 打开 Tools -> Options -> Debugging -> General

2)确保 ‘Suppress JIT optimization on module load’没有被选中

3)也确保‘Enable Just My Code’没有被选中

Vance 也建议我们进入advanced build设置release dll为pdb only,这时我们可以用前面同样的方式运行这段代码

用 JIT 看我们的代码另外一种方式是使用release模式,使用Start the executable without the debugger,再附加visualstudio到进程进行调试.

使用任一方法我们都能让 JIT 将代码优化了。得到优化的代码如下

for (int i = 0; i < 10; i++) { 00000000 push esi 00000001 xor esi,esi Console.WriteLine("Hello World!"); 00000003 cmp dword ptr ds:[02271084h],0 0000000a jne 00000016 0000000c mov ecx,1 00000011 call 786FC654 00000016 mov ecx,dword ptr ds:[02271084h] 0000001c mov edx,dword ptr ds:[0227307Ch] 00000022 mov eax,dword ptr [ecx] 00000024 call dword ptr [eax+000000D8h] for (int i = 0; i < 10; i++) { 0000002a add esi,1 0000002d cmp esi,0Ah 00000030 jl 00000003 00000032 pop esi } } 00000033 ret

哇,这次的代码比第一次少多了,JIT 优化确实工作的很好,这就是为什么查看实际的反编译代码而非IL是这样重要,因为JIT经常会通过识别IL中的模式来进行优化,机敏的读者可能注意在我们的循环的内部事实上产生了更多的密码。初看起来这是非常可怕的,但其实这说明优化器已经帮助我们inline了Console.WriteLine方法,实际是节省了很多代码在接下来的帖子中我会谈到inline,但是大家先明白这是一个很重要的优化

我们这时已经准备好了怎样在调试器中去欣赏优化的和没有优化的代码,我想这是一个好的开始,下面的几个帖子我会为更深入的了解JIT的一般优化过程而打好基础,我们也可以接触一些工具,看看他们会怎样帮助我们得到更好的代码

希望能在那里见你。

原文地址

http://codebetter.com/blogs/gregyoung/archive/2006/06/09/146298.aspx

原文的一些资源:

http://en.wikipedia.org/wiki/X86

http://www.codeguru.com/csharp/.net/net_general/il/article.php/c4635/ IL tutorial

http://burks.brighton.ac.uk/burks/language/asm/asmtut/asm1.htm ASM tutorial

转载于:https://www.cnblogs.com/yizhu2000/archive/2007/08/08/848160.html

[译]怎样用VisualStudio查看非托管代码相关推荐

  1. Win10缺失.Net处理方案汇总

    VisualStudio创建WindowsForm应用异常 问题主要表现在一下几个方面表现 正常的进入VisualStudio不再如任何项目时,没有任何的异常信息:创建或者打开控制台项目(.Net C ...

  2. 英读廊——吃醋的女朋友(A Jealous Girlfriend)

    * 用英语自身来理解和学习英语是最好的方式,<英读廊>是<满庭说英语>中的拓展阅读系列,这一系列的文章力求帮助大家在英语阅读能力上有所提升,并树立英语思维: * 推荐的阅读的方 ...

  3. C++实现文本界面英语词典

    C++实现文本界面英语词典 C++实现文本界面的英语词典,能在Dev-C++运行.提供两种方案:一是简单仅查词功能:二是具有查词.添加.删除功能,具有选择菜单,值得一提的是,本程序对用户输入菜单选项序 ...

  4. 开源文档翻译的质量保障实践

    本文也发在我的个人博客上:https://hltj.me/translate/2017/06/22/oss-docs-translate-quality.html . 五月份,我宣布了 Kotlin ...

  5. 江苏大学微型计算机控制技术,江苏大学计算机控制技术课程设计.doc

    实用文档 文案大全 计算机控制技术课程设计 姓 名: XXX 专 业: 自动化1101 学 号: 311 所在学院: 电气信息工程学院 2013年1月13日 目录 TOC \o "1-3&q ...

  6. 【真的?】用 ChatGPT 写一篇 Python 翻译库博客,可以打 9 分

    今天来个大的实践项目,用 ChatGPT 写一篇博客,冲击一下热榜! 从零开始玩 ChatGPT ⛳️ ChatGPT 亮点 ⛳️ 账号篇 ⛳️ 第一次使用 ⛳️ 用 Python 实现一个英汉互译的 ...

  7. VisualStudio 的 Spy++ —— 窗口、消息 的 查看分析利器

    spy++ 中文使用手册:https://pan.baidu.com/s/1NtLQMP1odHDAla4VH8m9Mg    提取码:294c Spy++使用方法:https://www.cnblo ...

  8. 查看mysql数据插入时间_[译] MySQL 最佳实践 —— 高效插入数据

    Get the dolphin up to speed - Photo by JIMMY ZHANG on Unsplash[1] 当你需要在 MySQL 数据库中批量插入数百万条数据时,你就会意识到 ...

  9. glibc版本查看_[译] 写一个简单的内存分配器(替换glibc中的malloc函数)

    本文介绍如何用c语言实现一个简单的内存分配器,可替换glibc中的 malloc(), calloc(), realloc(), free(). 这是一篇入门级别的文章,所以不会介绍所有的细节. 代码 ...

最新文章

  1. Github的Tom大鸟:我是如何拒绝微软30w的诱惑,专注于Github事业
  2. OJ在线编程----常见输入输出练习场
  3. 我终于加上博士大佬的微信!攒了近百个技术问题,一口气解决!(文末有福利)...
  4. ElasticSearch Java Api(二) -检索索引库
  5. 我用大屏模板做年中可视化报告,惊艳了在场的同事和领导
  6. C与PHP的联系与区别
  7. PCA和线性回归之间的关系如何?
  8. 95-40-032-java.util.concurrent-ConcurrentHashMap
  9. Java Garbage Collection基础详解------Java 垃圾回收机制技术详解
  10. U811.1接口EAI系列之三--采购订单生成--VB语言
  11. mysql connections
  12. oa系统在线试用,零成本开始研发协作免费试用
  13. mysql数据库设计与优化与架构 模拟场景(京东商城)
  14. SQL Server数据库备份工具
  15. extern “C“的作用及理解
  16. 如何实现图片的上传-(上传到本地)
  17. Vue单页面应用性能优化实践
  18. 2655 切木头(二分)
  19. mysql数据备份管理
  20. 最新研究报告:大数据 大而恒久才是美

热门文章

  1. ROS 用 roboware实现节点信息发送和接收
  2. MySql练习题参考答案
  3. 以SIGSEGV为例详解信号处理(与栈回溯)
  4. Kubecon 2017大会Google高级产品经理David Aronchick访谈:机器学习和Kubernetes
  5. rsyslog的配置文件使用方法
  6. Redis持久化方法对比分析
  7. 06.SQLServer性能优化之---数据库级日记监控
  8. 【推荐】MySQL Cluster报错及解决方法(不断更新中)
  9. sql算术运算符_SQL运算符教程–按位,比较,算术和逻辑运算符查询示例
  10. 聊天软交互原理_来自不同城市的人们如何在freeCodeCamp聊天室中进行交互