[译]有效的C#-性能说明
By robot-v1.0
本文链接 https://www.kyfws.com/best-practices/effective-c-performance-notes-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 13 分钟阅读 - 6221 个词 阅读量 0[译]有效的C#-性能说明
原文地址:https://www.codeproject.com/Articles/10896/Effective-C-Performance-notes
原文作者:Bill Wagner
译文由本站 robot-v1.0 翻译
前言
Don’t emphasize practices that may have an affect on performance in a few cases 在某些情况下,不要强调可能会影响绩效的做法
介绍(Introduction)
这个世界充满了听起来合理的建议,但实际上并非如此.很多时候,多次建议已经失去了建议的正当性,使它进入了我们的常规做法.当我们继续重复真理时,原因就永远消失了.我们都一遍又一遍地听到很多这样的建议:“过马路前要三思而后行”,“不要用剪刀奔跑”,您就会明白.技术建议也是如此.简单地重复您之前听到的内容,直到毫无疑问地被接受为止,是很容易的.(The world is full of advice that sounds reasonable, but really isn’t. Too often, the justification for a piece of advice is lost in the multiple retellings that cause it to enter our common practice. While we continue to repeat something as truth, the reasons are lost forever. We’ve all heard many of these bits of advice over and over: “Look both ways before crossing the street”, “Don’t run with scissors”, you get the idea. The same is true for technical advice. It’s easy to simply repeat what you’ve heard before until it becomes unquestionably accepted.)
错了(尽管我不建议您用剪刀跑步).(That’s wrong (although I don’t recommend running with scissors).)
所有建议均基于一些实际经验,包括说明为什么应遵循该建议的理由.当您只知道建议时,您将无法发现例外情况,也无法识别特定建议何时过时.(All advice is based on some real experience, including the justification of why that advice should be followed. When all you know is the advice, you can’t spot the exceptions, or recognize when a particular recommendation has become obsolete.)
这把我带到了(This brings me to the charter of the) 有效的软件开发系列(Effective Software Development Series) 和我自己的贡献(and my own contribution,) 有效的C#(Effective C#) . Scott Meyer对我的建议是专注于经验丰富的C#开发人员对其他C#开发人员的建议.尽管有效的书中包含了对实践开发人员最有用的内容,但很少有建议是普遍正确的.这就是为什么有效的书会详细说明每个项目的理由.该理由为读者提供了跟随或忽略任何单个项目所需的知识.许多有用的建议有多个简单的理由.一个很好的例子是对" as"和" is"关键字的建议,而不是强制转换.的确,这些关键字使您可以测试运行时类型信息,而无需编写try/catch块或从方法中引发异常.确实,有时在发现意外类型时抛出异常是正确的行为.但是异常的性能开销并不是这两个运算符的全部内容. as和is运算符执行运行时类型检查,而忽略任何用户定义的转换运算符.类型检查运算符的行为与强制转换不同.在有效C#中的每个项目中,您都会发现类似的细微之处.通过至少检查所有这些细节一次,您将更好地理解每条准则的合理性,以及您应该忽略特定建议的那些场合.(. Scott Meyer’s advice to me was to focus on the advice that experienced C# developers give to other C# developers. Yet even though Effective books include the most useful items for practicing developers, few recommendations are universally correct. That’s why Effective books go into detail on the justification of each item. That justification gives the reader the knowledge needed to follow or ignore any individual item. Many useful pieces of advice have more than one simple justification. A good example is the recommendations on the ‘as' and ‘is' keywords instead of casting. It’s true that those keywords let you test runtime type information without writing try / catch blocks or having exceptions thrown from your methods. It’s also true that there are times when throwing an exception is the proper behavior when you find an unexpected type. But the performance overhead of exceptions is not the whole story with these two operators. The as and is operators perform run time type checking, ignoring any user defined conversion operators. The type checking operators behave differently than casts. You’ll find similar subtleties in every item in Effective C#. By examining all those details at least once, you’ll better understand the justification of each guideline, and those occasions when you should ignore a particular recommendation.)
尽管上一段提到运行时性能是在不同语言结构中进行选择的一种理由,但值得注意的是,很少有有效项目是严格根据性能来进行辩护的.一个简单的事实是,低级优化不会是普遍的.低级语言构造将在编译器版本之间或在不同的使用场景下表现出不同的性能特征.简而言之,如果不进行概要分析和测试,则不应执行低级优化.但是,有些设计级指南可能会对程序性能产生重大影响.有效C#中的项目34讨论了有关Web服务API粒度的一些准则.这类设计问题可能会对面向服务的应用程序的性能产生重大影响.有效C#的第2章全都讨论了资源管理.这包括(Even though the previous paragraph mentions runtime performance as one justification for choosing among different language constructs, it’s worth noting that very few Effective Items are justified strictly based on performance. The simple fact is that low-level optimizations aren’t going to be universal. Low level language constructs will exhibit different performance characteristics between compiler versions, or in different usage scenarios. In short, low-level optimizations should not be performed without profiling and testing. However, there are design-level guidelines that can have a major impact on the performance of a program. Item 34 in Effective C# discusses some guidelines for the granularity of web service APIs. These kinds of design issues can have a large impact on the performance of service oriented application. All of chapter 2 in Effective C# discusses resource management. This includes) IDisposable
和终结器,以及高效的类和对象初始化.但是,这些指导原则并不严格地针对性能.它们包括确保您的应用程序健壮和正确并随时间推移更容易扩展的策略.(and finalizers, and efficient class and object initialization. But those guidelines are not strictly for performance; they include strategies to ensure that your applications are robust and correct, and more easily extended over time.)
本文的其余部分将研究您可能已经听到的一些常见建议.我将指出这些项目何时可能有效,何时进行跟踪会给您带来深重的麻烦.我将从本文中唯一的通用建议开始:(The remainder of this article will examine a few common recommendations you may have heard. I’ll point out when those items are likely valid, and when following them can get you in deep trouble. I’ll start with the only universal recommendation in this article:)
一个普遍的建议:优化意味着分析.(The one universal recommendation: Optimization means Profiling.)
这是实际测试代码更改的最佳实践的特例.修复错误时,请遵循可接受的一组步骤:通过测试验证该错误是否存在.您进行更改.您可以通过测试验证该错误是否已修复(还应该执行合理的回归测试,以确保您没有破坏其他任何东西.)如果您声称要优化代码,则必须执行相同的过程.您必须先进行性能测量,然后再进行更改,然后再进行性能测量.无论您遵循谁的推荐,都需要执行以下步骤.少做任何事情都不是工程.(This is a special case of the best practice that you actually test code changes. When you fix a bug, you follow an accepted set of steps: You verify that the bug exists by testing. You make the change. You verify that the bug is fixed by testing (You should also perform reasonable regression testing to make sure you didn’t break anything else.) The same process is necessary if you claim to be optimizing code. You must measure the performance before making the change, make the change, and then measure the performance again. No matter whose recommendation you are following, you need to do these steps. Anything less is simply not engineering.)
传统智慧:字符串连接非常昂贵(Conventional Wisdom: String Concatenation is expensive)
您无疑已经听说过字符串连接是一项昂贵的操作.每当您编写看起来可能会修改字符串内容的代码时,实际上就是在创建一个新的字符串对象,并将旧的字符串对象保留为垃圾.我在有效C#(项目16)中使用了以下示例:(You’ve undoubted heard that string concatenation is an expensive operation. Whenever you write code that appears to modify the contents of a string, you are actually creating a new string object and leaving the old string object as garbage. I used the following example in Effective C# (Item 16):)
string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();
就像您写的一样低效:(Is just as inefficient as if you had written:)
string msg = "Hello, ";
string tmp1 = new String( msg + thisUser.Name );
string msg = tmp1; // "Hello " is garbage.
string tmp2 = new String( msg + ". Today is " );
msg = tmp2; // "Hello <user>" is garbage.
string tmp3 = new String( msg + DateTime.Now.ToString( ) );
msg = tmp3; // "Hello <user>. Today is " is garbage.
琴弦(The strings) tmp1
,(,) tmp2
和(, and) tmp3
,以及最初建造的(, and the originally constructed) msg ("Hello")
都是垃圾字符串类上的+ =方法创建一个新的字符串对象并返回该字符串.它不会通过将字符连接到原始存储来修改现有字符串.对于上述简单的构造,您应该使用(are all garbage. The += method on the string class creates a new string object and returns that string. It does not modify the existing string by concatenating the characters to the original storage. For simple constructs like the one above, you should use the) string.Format()
方法:(method:)
string msg = string.Format ( "Hello, {0}. Today is {1}",
thisUser.Name, DateTime.Now.ToString( ));
对于更复杂的字符串操作,可以使用StringBuilder类:(For more complicated string operations you can use the StringBuilder class:)
StringBuilder msg = new StringBuilder( "Hello, " );
msg.Append( thisUser.Name );
msg.Append( ". Today is " );
msg.Append( DateTime.Now.ToString());
string finalMsg = msg.ToString();
最后,您有两种选择可以避免搅动字符串对象:(In the end, you have two choices to avoid churning through string objects:) string.Format
和(, and the) StringBuilder
类.我选择基于可读性.(class. I choose based on readability.)
传统智慧揭穿:检查Length属性比进行相等比较要快(Conventional Wisdom Debunked: Checking the Length property is faster than an equality comparison)
有人评论这个成语:(Some people have commented that this idiom:)
if ( str.Length != 0 )
比这更好:(is preferable to this one:)
if ( str != "" )
正常的理由是速度.人们会告诉您,检查字符串的长度比检查两个字符串是否相等要快.可能是正确的,但我真的怀疑您是否会看到任何可衡量的性能改进.两者都涉及一个函数调用,并且.NET Framework团队都可能对其进行了优化.我的个人喜好是使用(The normal justification is speed. People will tell you that checking the length of the string is faster than checking to see if two string are equal. That may be true, but I really doubt you’ll see any measurable performance improvement. Both involve one function call, and both are likely optimized well by the .NET Framework team. My one personal preference is to use the) String.Empty
这些比较的常量,因为我认为它更具可读性:(constant for these comparisons because I think it is more readable:)
if ( str != string.Empty )
传统智慧调查:String.Equal优于==(Conventional Wisdom Investigated: String.Equal is better than ==)
首先,请向您介绍有效C#中的项目9.我花了几页时间讨论了有关.NET中进行相等性比较的不同方法的所有细节.在这里,我将指出一些要考虑的规则.最重要的是:对于字符串类,(I’ll start by referring you to Item 9 in Effective C#. I spend several pages discussing all the gory details about different methods of doing equality comparisons in .NET. Here, I’ll point to a few rules to consider. The most important one is this: for the string class,) Operator ==
和(and) String.Equal()
将始终给出相同的结果.期.您可以互换使用,而不会影响程序的行为.您选择哪种格式取决于您自己的个人风格以及所使用的编译时类型.(will always give the same result. Period. You can use either interchangeably without affecting the behavior of your program. Which you pick will depend on your own personal style, and the compile-time types you’re working with.)
的(The) ==
运算符是强类型的,并且仅当两个参数都是字符串时才编译.这意味着在其实现内部没有强制转换或转换.(operator is strongly typed, and will only compile if both parameters are strings. That implies that there are no casts or conversions inside its implementation.) String.Equal
另一方面,具有多个重载.有一个强类型的版本,另一个版本覆盖了(, on the other hand, has multiple overloads. There is a strongly-typed version, and another version that overrides the) System.Object.Equals()
方法.如果编写的代码调用了Object版本的替代,则将付出少量的性能损失.之所以要付出这种代价,是因为虚函数调用会导致(轻微)性能下降,而转换会导致另一个(轻微)性能下降.当然,如果任何一个操作数都具有对象的编译时类型,则无论如何都要支付转换费用,所以没什么大不了的.(method. If you write code that calls the override of the Object version, you will pay a small performance penalty. That penalty comes because there is a (slight) performance hit for the virtual function call, and another (slight) performance hit for the conversion. Of course, if either operand has the compile-time type of object, you’ll pay for the conversion costs anyway, so it doesn’t much matter.)
关于相等性和字符串的底线是,如果您使用与正在使用的编译时类型相匹配的方法,则肯定会获得预期的行为,并且可能会获得最佳性能.如果需要所有详细信息,请参见有效C#中的项目9.(The bottom line on equality and strings is that if you use the method that matches the compile time types you’re working with, you will certainly get the expected behavior, and you’ll likely get the best performance. If you want all the details, see Item 9 in Effective C#.)
传统智慧:装箱和拆箱都是不好的(Conventional Wisdom: Boxing and Unboxing are bad)
的确如此,装箱和拆箱通常与程序中的不良行为相关联.太多次,理由就是性能.是的,装箱和拆箱会导致性能问题.但是有时候,创建自己的集合类或求助于数组同样会很糟糕.这取决于您多长时间访问一次集合中的值.就一次?然后使用(This is true, boxing and unboxing are often associated with negative behaviors in your program. Too many times, the justification is performance. Yes, boxing and unboxing cause performance issues. Sometimes though, creating your own collection classes or resorting to arrays will be just as bad. It depends on how often you access the values in the collection. Just once? Then use the) ArrayList
类.你可能会没事的.几百次?考虑一个自定义集合.几百次,但是您还要将集合的大小更改几百次吗?我不知道哪个更好,这就是为什么要进行简介.(class. You’ll probably come out fine. Several hundred times? Think about a custom collection. Several hundred times, but you’re also changing the size of the collection several hundred times? I’ve got no idea which is better, which is why you profile.)
比性能更重要的是正确性.我在有效C#(pp 106-107)中展示了两个不同的示例,其中装箱和拆箱会导致应用程序中的错误行为.查找装箱和拆箱很棘手,因为编译器无法为您提供帮助,并且它确实会在多个地方发生.每个人都已经知道收集类将如何导致装箱和拆箱.但是,当您通过接口访问值类型以及将值传递给期望的方法时,也会产生相同的效果.(More important than performance is correctness. I showed two different examples in Effective C# (pp 106-107) where boxing and unboxing can cause incorrect behavior in your application. Looking for boxing and unboxing is tricky because the compiler won’t help you, and it does occur in multiple places. Everyone has seen how the collection classes will cause boxing and unboxing to occur. But, the same effect happens when you access a value type through an interface and when you pass values to methods that expect) System.Object
.例如,此行将框装箱和拆箱三遍:(. For example, this line boxes and unboxes three times:)
Console.WriteLine("A few numbers:{0}, {1}, {2}", 25, 32, 50);
应避免装箱和拆箱,但这与您应避免使用(Boxing and unboxing are to be avoided, but that’s not the same as saying you should avoid using the) Systems.Collections
值的类.有更多的方法来对值进行装箱,有时节省代码是值得的.(classes for values. There are more ways to box values, and sometimes the code savings is worth the performance costs.)
传统智慧解释:“按原样"与"按原样"与强制转换(Conventional Wisdom Explained: ‘as' and ‘is' vs. casting)
我不会在这里讨论性能,因为这不是选择一个或另一个的原因. as和is运算符不检查用户定义的转换运算符.强制转换可以同时使用显式和隐式转换运算符.用户定义的转换和强制转换的确切行为变得复杂,并且依赖于参数的编译时类型.我在有效C#(第3项)的第18-24页上讨论了所有详细信息.(I’m not going to discuss performance here, because that’s not the reason to pick one or the other. The as and is operators do not examine user defined conversion operators. Casts can make use of both explicit and implicit conversion operators. The exact behavior of user defined conversions and casts gets complicated, and relies on the compile-time type of the arguments. I discussed all the details on pages 18 – 24 of Effective C# (Item 3).) 下一个重要的问题是,如果转换失败,是否为错误条件.如果接收非预期类型的输入是一种预期行为,则as和is运算符提供的检查要比引入异常更简单.但是,如果要编写此代码:(The next important question is whether or not it’s an error condition if the conversion fails. If it’s an expected behavior to receive inputs that are not the expected type, the as and is operators provide simpler checks than introducing exceptions. However, if you’re going to write this:)
if ( ! o is ExpectedType )
throw new ArgumentTypeException( "You didn't use the right type" );
请直接使用演员表.更清楚了在相关说明中,请考虑(Please just use the cast instead. It’s clearer. On a related note, consider the difference between) double.Parse()
和(and) double.TryParse()
.一个假设成功,失败则抛出异常.其他返回错误代码以指示问题.(. One assumes success and throws exceptions when it fails. The other returns error codes to indicate a problem.)
传统智慧揭穿:for循环比foreach更快(Conventional Wisdom Debunked: for loops are faster than foreach)
我不会这么说(I’m not going to say that) foreach
是比较快的.但是,我要说的是,我很高兴在编译器编写者努力优化最通用的语言构造的环境中工作. C#1.1编译器对以下代码生成的代码进行了优化:(is faster. However, I am going to say I’m happy to work in an environment where the compiler writers work hard to optimize the language constructs that are the most versatile. The C# 1.1 compiler added optimizations to the code generated by) foreach
循环和数组.在1.0版中,for循环要快得多.这不再是真的. (还记得早先的建议来分析您的代码吗?)(loops and arrays. In version 1.0, the for loops were much faster. That’s no longer true. (Remember that earlier recommendation to profile your code?))
最佳实践是编写尽可能清晰的代码,并期望编译器正确优化.如有必要,剖析并改进.(The best practice here is to write the clearest code you can, and expect the compiler to optimize properly. If necessary, profile and improve.)
传统智慧揭穿:NGen将提高性能(Conventional Wisdom Debunked: NGen will improve performance)
也许.再说一次,也许不是.(Maybe. Then again, maybe not.) 这是一个危险的建议.是的,nGen确实可以减少某些程序集的启动时间.的确,您将失去一些JIT编译的优势. JIT编译器只能在运行时执行许多优化.(This is a dangerous recommendation. Yes, it’s true that nGen will lower the startup time for some assemblies. It’s also true that you will lose some of the advantages of JIT compilation. There are a number of optimizations that the JIT compiler can perform only at runtime.) 本白皮书(This whitepaper) 很好地概述了将JIT步骤留给运行时的优点.(gives a good overview of the advantages of leaving the JIT step to runtime.) 这是一个仅建议性能的建议,如果没有对两个版本(启动时间和运行时间)进行概要分析,我绝不会盲目遵循.另外,您应该剖析NGen’ed和常规装配的组合.(This is a performance only recommendation that I would never blindly follow without profiling both versions, both startup times and operating times. In addition, you should profile combinations of NGen’ed and regular assemblies.)
结论(Conclusion)
正如我在开篇中所说的,一本有效的书的目的是为大多数开发人员提供最有用的建议.该建议必须与技术信息相符,以帮助读者决定何时应用该建议以及在什么情况下可以安全地忽略该建议.我向您介绍了我在<有效C#>中为读者提供的理由,以及有时与在C#开发人员中经常重复提出的建议有何冲突. Addison-Wesley,The Code Project和我正在努力为Code Project读者摘录此处的一些Effective C#项目.(As I said in my opening the purpose of an Effective book is to provide the most useful pieces of advice to the most developers. That advice must be justified with the technical information to help readers decide when advice applies, and under what scenarios it can be safely ignored. I’ve given you a taste of the justification I gave readers in Effective C#, and how that sometimes conflicts with the recommendations often repeated among C# developers. Addison-Wesley, The Code Project and I are working to excerpt some of the Effective C# items here for Code Project readers.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET Windows Visual-Studio QA Dev 新闻 翻译