《Effective C#》读书笔记——条目11:理解短小方法的优势<C#语言习惯>
2012-10-08 23:14
453 查看
在应用程序开发过程中,开发者都力求写出更加高效的代码。但是当你想手工为C#编译器优化代码时,你的种种优化可能反倒会阻碍JIT进行更加高效的优化。因此,我们最好尽可能的写出最清晰的代码,将优化工作交给JIT编译器去完成。
在.NET平台下开发程序的开发者都应该知道:.NET运行时将调用JIT编译器来将C#编译器生成的IL翻译成机器码。JIT不会在程序刚开始的时候就完全翻译所有的IL代码,CLR根据函数的粒度来逐一进行JIT编译。没有被调用的函数根本不会被JIT编译,因此将那些非常重要的逻辑分解成更多的小方法要比把所有逻辑放在一起形成大型复杂函数更有效率。例如下面的代码:
在第一次调用BuildMsg时,if-else两个分支都将被JIT编译。而实际上仅需要编译其中的一个分支就足够了,我们可以拆分这个方法,对其进行优化,下面是优化后的代码:
View Code
这时候两个方法可以根据需要再进行JIT编译,而不必在第一次调用BuildMsg方法是进行。我们可以看出:更小的函数让JIT编译器更方便的根据需要进行编译,而不是将时间浪费在不急于一时使用的代码上。对于switch语句中的每个case中的代码,这个规则的影响更明显。
方法越简单就越适合内联。不过虚方法和包含ctry/catch代码块的方法将不会被内联。内联也改变了:代码在执行时才会被JIT编译 这一原则。所以在.NET平台下编程我们的责任应该就是尽量编写短小精悍的方法,而为你的算法生成高效的机器码是C#编译器和JIT编译器的责任。
将C#代码翻译为可执行的机器码有两个步骤:1.C#编译器将代码生成为IL,并放在程序集中。2.JIT再根据需要逐一为方法(或是一组方法,如果涉及内联)生成机器码。短小的方法让JIT编译器能够更好的平摊编译的代价。短小的代码也更适合内联。方法除了短小之外,简化控制流程也很重要,控制的分支越少JIT编译器也更容易选择找到最适合放在寄存器中的变量。因此,编写短小精悍的代码不但影响代码的可读性,也影响到程序运行的效率。
在.NET平台下开发程序的开发者都应该知道:.NET运行时将调用JIT编译器来将C#编译器生成的IL翻译成机器码。JIT不会在程序刚开始的时候就完全翻译所有的IL代码,CLR根据函数的粒度来逐一进行JIT编译。没有被调用的函数根本不会被JIT编译,因此将那些非常重要的逻辑分解成更多的小方法要比把所有逻辑放在一起形成大型复杂函数更有效率。例如下面的代码:
public string BuildMsg(bool takeFirstPath) { StringBuilder msg = new StringBuilder(); if (takeFirstPath) { msg.Append("A problem occurred."); msg.Append("\nThis is a problem."); msg.Append("imagine much more text"); } else { msg.Append("This Path is not so bad."); msg.Append("\nIt is only a minor inconvenience."); msg.Append("Add more detailed diagnostics here."); } return msg; }
在第一次调用BuildMsg时,if-else两个分支都将被JIT编译。而实际上仅需要编译其中的一个分支就足够了,我们可以拆分这个方法,对其进行优化,下面是优化后的代码:
View Code
public string BuildMsg(bool takeFirstPath) { if (takeFirstPath) { return FirstPath(); } else { return SecondPath(); } } public string FirstPath() { StringBuilder msg = new StringBuilder(); msg.Append("A problem occurred."); msg.Append("\nThis is a problem."); msg.Append("imagine much more text"); return msg.ToString(); } public string SecondPath() { StringBuilder msg = new StringBuilder(); msg.Append("This Path is not so bad."); msg.Append("\nIt is only a minor inconvenience."); msg.Append("Add more detailed diagnostics here."); return msg.ToString(); } }
这时候两个方法可以根据需要再进行JIT编译,而不必在第一次调用BuildMsg方法是进行。我们可以看出:更小的函数让JIT编译器更方便的根据需要进行编译,而不是将时间浪费在不急于一时使用的代码上。对于switch语句中的每个case中的代码,这个规则的影响更明显。
寄存器的优化
小而简单的方法会让JIT更容易的进行寄存器的选择工作,即选择哪个局部变量可以存放在寄存器中,而不是栈上。越少使用局部变量,也就让JIT编译器能够更方便的找到最适合放在寄存器的那一些。而越小的函数包含的局部变量也越少,也就更方便JIT对寄存器进行优化。内联的优化
内联表示把函数体替换到函数被调用的位置。由JIT编译器负责决定哪些方法应该被内联,当内联可以有效提高效率时,JIT编译器将自动执行。不过内联的标准并不是固定的,且当前的规则也不能保证将来不会发生变化,此外,是否内联完全由JIT自己决定。不过我们可以使用下面的特性选项通知JIT不要内联某个方法:[MethodImpl(MethodImplOptions.NoInlining)]
方法越简单就越适合内联。不过虚方法和包含ctry/catch代码块的方法将不会被内联。内联也改变了:代码在执行时才会被JIT编译 这一原则。所以在.NET平台下编程我们的责任应该就是尽量编写短小精悍的方法,而为你的算法生成高效的机器码是C#编译器和JIT编译器的责任。
小节:
将C#代码翻译为可执行的机器码有两个步骤:1.C#编译器将代码生成为IL,并放在程序集中。2.JIT再根据需要逐一为方法(或是一组方法,如果涉及内联)生成机器码。短小的方法让JIT编译器能够更好的平摊编译的代价。短小的代码也更适合内联。方法除了短小之外,简化控制流程也很重要,控制的分支越少JIT编译器也更容易选择找到最适合放在寄存器中的变量。因此,编写短小精悍的代码不但影响代码的可读性,也影响到程序运行的效率。
相关文章推荐
- 《Effective C#》读书笔记——条目6:理解几个等同性判断之间的关系<C#语言习惯>
- 《Effective C#》读书笔记——条目5:为类型提供ToString()方法<C#语言习惯>
- 《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量<C#语言习惯>
- 《Effective C#》读书笔记——条目1:使用属性而不是可访问的数据成员<C#语言习惯>
- 《Effective C#》读书笔记——条目3:推荐使用is或as而不是强制转换类型<C#语言习惯>
- 《Effective C#》读书笔记——条目23:理解接口方法和虚方法的区别<使用C#表达设计>
- 《Effective C#》读书笔记——条目2:用运行时常量而不是编译期常量<C#语言习惯>
- 《Effective C#》读书笔记——条目8:推荐使用查询语法而不是循环<C#语言习惯>
- 《Effective C#》读书笔记——条目4:使用Conditional特性而不是#if条件编译<C#语言习惯>
- 《Effective C#》读书笔记——条目27:让类型支持序列化<使用C#表达设计>
- 《Effective C#》读书笔记——条目28:提供粗粒度的互联网API<使用C#表达设计>
- 《Effective C#》读书笔记——条目25:用事件模式实现通知<使用C#表达设计>
- 《Effective C#》读书笔记——条目26:避免返回对内部类对象的引用<使用C#表达设计>
- 《Effective C#》读书笔记——条目22:通过定义并实现接口替代继承<使用C#表达设计>
- 深入理解 c# 第五章 匿名方法用于Action<T>委托类型 反转字符 求平方根 求平均数
- 【C#】对异步请求处理程序IHttpAsyncHandler的理解和分享一个易用性封装 【手记】走近科学之为什么明明实现了IEnumerable<T>的类型却不能调用LINQ扩展方法 【手记】手机网页弹出层后屏蔽底层的滑动响应 【手记】ASP.NET提示“未能创建类型”处理 【Web】一个非常简单的移动web消息框 【手记】解决EXCEL跑SQL遇“查询无法运行或数据库表无法打开...”
- 《Effective C#》读书笔记——条目21:限制类型的可见性<使用C#表达设计>
- 《Effective C#》读书笔记——条目24:用委托实现回调<使用C#表达设计>
- 深入理解 c# 第一章 使用扩展方法对 List<Product> 排序
- C#操作字符串方法总结<转>