怎么做搜索功能网站,阿里云域名续费,介绍自己的做的网站吗,外贸seo优化方法摘要#xff1a;本文分享了性能优化的一些建议和思考#xff0c;比如不要过早优化、好工具很重要、性能的关键#xff0c;在于内存分配等。开发者不要盲目的没有根据的优化#xff0c;首先定位和查找到造成产生性能问题的原因点最重要。
【编者按】Bill Chiles#xff08…摘要本文分享了性能优化的一些建议和思考比如不要过早优化、好工具很重要、性能的关键在于内存分配等。开发者不要盲目的没有根据的优化首先定位和查找到造成产生性能问题的原因点最重要。
【编者按】Bill ChilesRoslyn编译器的程序经理)写了一篇文章《Essential Performance Facts and .NET Framework Tips》知名博主寒江独钓对该文进行了摘译文中分享了性能优化的一些建议和思考比如不要过早优化、好工具很重要、性能的关键在于内存分配等并指出开发者不要盲目的没有根据的优化首先定位和查找到造成产生性能问题的原因点最重要。 全文如下
本文提供了一些性能优化的建议这些经验来自于使用托管代码重写C# 和 VB编译器并以编写C# 编译器中的一些真实场景作为例子来展示这些优化经验。.NET 平台开发应用程序具有极高的生产力。.NET 平台上强大安全的编程语言以及丰富的类库使得开发应用变得卓有成效。但是能力越大责任越大。我们应该使用.NET框架的强大能力但同时如果我们需要处理大量的数据比如文件或者数据库也需要准备对我们的代码进行调优。
为什么来自新的编译器的性能优化经验也适用于您的应用程序
微软使用托管代码重写了C#和Visual Basic的编译器并提供了一些列新的API来进行代码建模和分析、开发编译工具使得Visual Studio具有更加丰富的代码感知的编程体验。重写编译器并且在新的编译器上开发Visual Studio的经验使得我们获得了非常有用的性能优化经验这些经验也能用于大型的.NET应用或者一些需要处理大量数据的APP上。你不需要了解编译器也能够从C#编译器的例子中得出这些见解。
Visual Studio使用了编译器的API来实现了强大的智能感知(Intellisense)功能如代码关键字着色语法填充列表错误波浪线提示参数提示代码问题及修改建议等这些功能深受开发者欢迎。Visual Studio在开发者输入或者修改代码的时候会动态的编译代码来获得对代码的分析和提示。
当用户和App进行交互的时候通常希望软件具有好的响应性。输入或者执行命令的时候应用程序界面不应该被阻塞。帮助或者提示能够迅速显示出来或者当用户继续输入的时候停止提示。现在的App应该避免在执行长时间计算的时候阻塞UI线程从而让用户感觉程序不够流畅。
想了解更多关于新的编译器的信息可以访问 .NET Compiler Platform (Roslyn)
基本要领
在对.NET 进行性能调优以及开发具有良好响应性的应用程序的时候请考虑以下这些基本要领
要领一:不要过早优化
编写代码比想象中的要复杂的多代码需要维护调试及优化性能。 一个有经验的程序员通常会对自然而然的提出解决问题的方法并编写高效的代码。 但是有时候也可能会陷入过早优化代码的问题中。比如有时候使用一个简单的数组就够了非要优化成使用哈希表有时候简单的重新计算一下可以非要使用复杂的可能导致内存泄漏的缓存。发现问题时应该首先测试性能问题然后再分析代码。
要领二:没有评测便是猜测
剖析和测量不会撒谎。测评可以显示CPU是否满负荷运转或者是存在磁盘I/O阻塞。测评会告诉你应用程序分配了什么样的以及多大的内存以及是否CPU花费了很多时间在 垃圾回收上。
应该为关键的用户体验或者场景设置性能目标并且编写测试来测量性能。通过使用科学的方法来分析性能不达标的原因的步骤如下使用测评报告来指导假设可能出现的情况并且编写实验代码或者修改代码来验证我们的假设或者修正。如果我们设置了基本的性能指标并且经常测试就能够避免一些改变导致性能的回退(regression)这样就能够避免我们浪费时间在一些不必要的改动中。
要领三:好工具很重要
好的工具能够让我们能够快速的定位到影响性能的最大因素(CPU内存磁盘)并且能够帮助我们定位产生这些瓶颈的代码。微软已经发布了很多性能测试工具比如 Visual Studio Profiler, Windows Phone Analysis Tool, 以及 PerfView.
PerfView是一款免费且性能强大的工具他主要关注影响性能的一些深层次的问题(磁盘 I/OGC 事件内存)后面会展示这方面的例子。我们能够抓取性能相关的 Event Tracing for Windows(ETW)事件并能以应用程序进程堆栈线程的尺度查看这些信息。PerfView能够展示应用程序分配了多少以及分配了何种内存以及应用程序中的函数以及调用堆栈对内存分配的贡献。这些方面的细节您可以查看随工具下载发布的关于PerfView的非常详细的帮助Demo以及视频教程比如 Channel9上的视频教程
要领四所有的都与内存分配相关
你可能会想编写响应及时的基于.NET的应用程序关键在于采用好的算法比如使用快速排序替代冒泡排序但是实际情况并不是这样。编写一个响应良好的app的最大因素在于内存分配特别是当app非常大或者处理大量数据的时候。
在使用新的编译器API开发响应良好的IDE的实践中大部分工作都花在了如何避免开辟内存以及管理缓存策略。PerfView追踪显示新的C# 和VB编译器的性能基本上和CPU的性能瓶颈没有关系。编译器在读入成百上千甚至上万行代码读入元数据活着产生编译好的代码这些操作其实都是I/O bound 密集型。UI线程的延迟几乎全部都是由于垃圾回收导致的。.NET框架对垃圾回收的性能已经进行过高度优化他能够在应用程序代码执行的时候并行的执行垃圾回收的大部分操作。但是单个内存分配操作有可能会触发一次昂贵的垃圾回收操作这样GC会暂时挂起所有线程来进行垃圾回收(比如 Generation 2型的垃圾回收)
常见的内存分配以及例子
这部分的例子虽然背后关于内存分配的地方很少。但是如果一个大的应用程序执行足够多的这些小的会导致内存分配的表达式那么这些表达式会导致几百M甚至几G的内存分配。比如在性能测试团队把问题定位到输入场景之前一分钟的测试模拟开发者在编译器里面编写代码会分配几G的内存。
装箱
装箱发生在当通常分配在线程栈上或者数据结构中的值类型或者临时的值需要被包装到对象中的时候比如分配一个对象来存放数据活着返回一个指针给一个Object对象。.NET框架由于方法的签名或者类型的分配位置有些时候会自动对值类型进行装箱。将值类型包装为引用类型会产生内存分配。.NET框架及语言会尽量避免不必要的装箱但是有时候在我们没有注意到的时候会产生装箱操作。过多的装箱操作会在应用程序中分配成M上G的内存这就意味着垃圾回收的更加频繁也会花更长时间。
在PerfView中查看装箱操作只需要开启一个追踪trace然后查看应用程序名字下面的GC Heap Alloc 项记住PerfView会报告所有的进程的资源分配情况如果在分配相中看到了一些诸如System.Int32和System.Char的值类型那么就发生了装箱。选择一个类型就会显示调用栈以及发生装箱的操作的函数。
例1 string方法和其值类型参数
下面的示例代码演示了潜在的不必要的装箱以及在大的系统中的频繁的装箱操作。 [js] view plaincopy public class Logger { public static void WriteLine(string s) { /*...*/ } } public class BoxingExample { public void Log(int id, int size) { var s string.Format({0}:{1}, id, size); Logger.WriteLine(s); } } 这是一个日志基础类因此app会很频繁的调用Log函数来记日志可能该方法会被调用millons次。问题在于调用string.Format方法会调用其 重载的接受一个string类型和两个Object类型的方法 [js] view plaincopy String.Format Method (String, Object, Object) 该重载方法要求.NET Framework 把int型装箱为object类型然后将它传到方法调用中去。为了解决这一问题方法就是调用id.ToString()和size.ToString()方法然后传入到string.Format 方法中去调用ToString()方法的确会导致一个string的分配但是在string.Format方法内部不论怎样都会产生string类型的分配。
你可能会认为这个基本的调用string.Format 仅仅是字符串的拼接所以你可能会写出这样的代码 [js] view plaincopy var s id.ToString() : size.ToString(); 实际上上面这行代码也会导致装箱因为上面的语句在编译的时候会调用 [js] view plaincopy string.Concat(Object, Object, Object); 这个方法.NET Framework 必须对字符常量进行装箱来调用Concat方法。
解决方法
完全修复这个问题很简单将上面的单引号替换为双引号即将字符常量换为字符串常量就可以避免装箱因为string类型的已经是引用类型了。 [js] view plaincopy var s id.ToString() : size.ToString(); 例2 枚举类型的装箱
下面的这个例子是导致新的C# 和VB编译器由于频繁的使用枚举类型特别是在Dictionary中做查找操作时分配了大量内存的原因。 [js] view plaincopy public enum Color { Red, Green, Blue } public class BoxingExample { private string name; private Color color; public override int GetHashCode() { return name.GetHashCode() ^ color.GetHashCode(); } } 问题非常隐蔽PerfView会告诉你enmu.GetHashCode()由于内部实现的原因产生了装箱操作该方法会在底层枚举类型的表现形式上进行装箱如果仔细看PerfView会看到每次调用GetHashCode会产生两次装箱操作。编译器插入一次.NET Framework插入另外一次。
解决方法
通过在调用GetHashCode的时候将枚举的底层表现形式进行强制类型转换就可以避免这一装箱操作。 [js] view plaincopy ((int)color).GetHashCode() 另一个使用枚举类型经常产生装箱的操作时enum.HasFlag。传给HasFlag的参数必须进行装箱在大多数情况下反复调用HasFlag通过位运算测试非常简单和不需要分配内存。
要牢记基本要领第一条不要过早优化。并且不要过早的开始重写所有代码。 需要注意到这些装箱的耗费只有在通过工具找到并且定位到最主要问题所在再开始修改代码。
字符串
字符串操作是引起内存分配的最大元凶之一通常在PerfView中占到前五导致内存分配的原因。应用程序使用字符串来进行序列化表示JSON和REST。在不支持枚举类型的情况下字符串可以用来与其他系统进行交互。当我们定位到是由于string操作导致对性能产生严重影响的时候需要留意string类的Format(),Concat(),Split(),Join(),Substring()等这些方法。使用StringBuilder能够避免在拼接多个字符串时创建多个新字符串的开销但是StringBuilder的创建也需要进行良好的控制以避免可能会产生的性能瓶颈。
例3 字符串操作
在C#编译器中有如下方法来输出方法前面的xml格式的注释。 [js] view plaincopy public void WriteFormattedDocComment(string text) { string[] lines text.Split(new[] {\r\n, \r, \n}, StringSplitOptions.None); int numLines lines.Length; bool skipSpace true; if (lines[0].TrimStart().StartsWith(///)) { for (int i 0; i numLines; i) { string trimmed lines[i].TrimStart(); if (trimmed.Length 4 || !char.IsWhiteSpace(trimmed[3])) { skipSpace false; break; } } int substringStart skipSpace ? 4 : 3; for (int i 0; i numLines; i) Console.WriteLine(lines[i].TrimStart().Substring(substringStart)); } else { /* ... */ } } 可以看到在这片代码中包含有很多字符串操作。代码中使用类库方法来将行分割为字符串来去除空格来检查参数text是否是XML文档格式的注释然后从行中取出字符串处理。
在WriteFormattedDocComment方法每次被调用时第一行代码调用Split()就会分配三个元素的字符串数组。编译器也需要产生代码来分配这个数组。因为编译器并不知道如果Splite()存储了这一数组那么其他部分的代码有可能会改变这个数组这样就会影响到后面对WriteFormattedDocComment方法的调用。每次调用Splite()方法也会为参数text分配一个string然后在分配其他内存来执行splite操作。
WriteFormattedDocComment方法中调用了三次TrimStart()方法在内存环中调用了两次这些都是重复的工作和内存分配。更糟糕的是TrimStart()的无参重载方法的签名如下 [js] view plaincopy namespace System { public class String { public string TrimStart(params char[] trimChars); } } 该方法签名意味着每次对TrimStart()的调用都回分配一个空的数组以及返回一个string类型的结果。
最后调用了一次Substring()方法这个方法通常会导致在内存中分配新的字符串。
解决方法
和前面的只需要小小的修改即可解决内存分配的问题不同。在这个例子中我们需要从头看查看问题然后采用不同的方法解决。比如可以意识到WriteFormattedDocComment()方法的参数是一个字符串它包含了方法中需要的所有信息因此代码只需要做更多的index操作而不是分配那么多小的string片段。
下面的方法并没有完全解但是可以看到如何使用类似的技巧来解决本例中存在的问题。C#编译器使用如下的方式来消除所有的额外内存分配。 [js] view plaincopy private int IndexOfFirstNonWhiteSpaceChar(string text, int start) { while (start text.Length char.IsWhiteSpace(text[start])) start; return start; } private bool TrimmedStringStartsWith(string text, int start, string prefix) { start IndexOfFirstNonWhiteSpaceChar(text, start); int len text.Length - start; if (len prefix.Length) return false; for (int i 0; i len; i) { if (prefix[i] ! text[start i]) return false; } return true; } WriteFormattedDocComment() 方法的第一个版本分配了一个数组几个子字符串一个trim后的子字符串以及一个空的params数组。也检查了”///”。修改后的代码仅使用了index操作没有任何额外的内存分配。它查找第一个非空格的字符串然后逐个字符串比较来查看是否以”///”开头。和使用TrimStart()不同修改后的代码使用IndexOfFirstNonWhiteSpaceChar方法来返回第一个非空格的开始位置通过使用这种方法可以移除WriteFormattedDocComment()方法中的所有额外内存分配。
例4 StringBuilder
本例中使用StringBuilder。下面的函数用来产生泛型类型的全名 [js] view plaincopy public class Example { // Constructs a name like SomeTypeT1, T2, T3 public string GenerateFullTypeName(string name, int arity) { StringBuilder sb new StringBuilder(); sb.Append(name); if (arity ! 0) { sb.Append(); for (int i 1; i arity; i) { sb.Append(T); sb.Append(i.ToString()); sb.Append(, ); } sb.Append(T); sb.Append(i.ToString()); sb.Append(); } return sb.ToString(); } } 注意力集中到StringBuilder实例的创建上来。代码中调用sb.ToString()会导致一次内存分配。在StringBuilder中的内部实现也会导致内部内存分配但是我们如果想要获取到string类型的结果化这些分配无法避免。
解决方法
要解决StringBuilder对象的分配就使用缓存。即使缓存一个可能被随时丢弃的单个实例对象也能够显著的提高程序性能。下面是该函数的新的实现。除了下面两行代码其他代码均相同 [js] view plaincopy // Constructs a name like FooT1, T2, T3 public string GenerateFullTypeName(string name, int arity) { StringBuilder sb AcquireBuilder(); /* Use sb as before */ return GetStringAndReleaseBuilder(sb); } 关键部分在于新的 AcquireBuilder()和GetStringAndReleaseBuilder()方法 [js] view plaincopy [ThreadStatic] private static StringBuilder cachedStringBuilder; private static StringBuilder AcquireBuilder() { StringBuilder result cachedStringBuilder; if (result null) { return new StringBuilder(); } result.Clear(); cachedStringBuilder null; return result; } private static string GetStringAndReleaseBuilder(StringBuilder sb) { string result sb.ToString(); cachedStringBuilder sb; return result; } 上面方法实现中使用了 thread-static字段来缓存StringBuilder对象这是由于新的编译器使用了多线程的原因。很可能会忘掉这个ThreadStatic声明。Thread-static字符为每个执行这部分的代码的线程保留一个唯一的实例。
如果已经有了一个实例那么AcquireBuilder()方法直接返回该缓存的实例在清空后将该字段或者缓存设置为null。否则AcquireBuilder()创建一个新的实例并返回然后将字段和cache设置为null 。
当我们对StringBuilder处理完成之后调用GetStringAndReleaseBuilder()方法即可获取string结果。然后将StringBuilder保存到字段中或者缓存起来然后返回结果。这段代码很可能重复执行从而创建多个StringBuilder对象虽然很少会发生。代码中仅保存最后被释放的那个StringBuilder对象来留作后用。新的编译器中这种简单的的缓存策略极大地减少了不必要的内存分配。.NET Framework 和 MSBuild中的部分模块也使用了类似的技术来提升性能。
简单的缓存策略必须遵循良好的缓存设计因为他有大小的限制cap。使用缓存可能比之前有更多的代码也需要更多的维护工作。我们只有在发现这是个问题之后才应该采缓存策略。PerfView已经显示出StringBuilder对内存的分配贡献相当大。 LINQ和Lambdas表达式 使用LINQ 和Lambdas表达式是C#语言强大生产力的一个很好体现但是如果代码需要执行很多次的时候可能需要对LINQ或者Lambdas表达式进行重写。
例5 Lambdas表达式ListT以及IEnumerableT
下面的例子使用 LINQ以及函数式风格的代码来通过编译器模型给定的名称来查找符号。 [js] view plaincopy class Symbol { public string Name { get; private set; } /*...*/ } class Compiler { private ListSymbol symbols; public Symbol FindMatchingSymbol(string name) { return symbols.FirstOrDefault(s s.Name name); } } 新的编译器和IDE 体验基于调用FindMatchingSymbol这个调用非常频繁在此过程中这么简单的一行代码隐藏了基础内存分配开销。为了展示这其中的分配我们首先将该单行函数拆分为两行 [js] view plaincopy FuncSymbol, bool predicate s s.Name name; return symbols.FirstOrDefault(predicate); 第一行中 lambda表达式“ss.Namename” 是对本地变量name的一个 闭包。这就意味着需要分配额外的对象来为 委托对象predict分配空间需要一个分配一个静态类来保存环境从而保存name的值。编译器会产生如下代码 [js] view plaincopy // Compiler-generated class to hold environment state for lambda private class Lambda1Environment { public string capturedName; public bool Evaluate(Symbol s) { return s.Name this.capturedName; } } // Expanded FuncSymbol, bool predicate s s.Name name; Lambda1Environment l new Lambda1Environment() { capturedName name }; var predicate new FuncSymbol, bool(l.Evaluate); 两个new操作符第一个创建一个环境类第二个用来创建委托很明显的表明了内存分配的情况。
现在来看看FirstOrDefault方法的调用他是IEnumerableT类的扩展方法这也会产生一次内存分配。因为FirstOrDefault使用IEnumerableT作为第一个参数可以将上面的展开为下面的代码 [js] view plaincopy // Expanded return symbols.FirstOrDefault(predicate) ... IEnumerableSymbol enumerable symbols; IEnumeratorSymbol enumerator enumerable.GetEnumerator(); while (enumerator.MoveNext()) { if (predicate(enumerator.Current)) return enumerator.Current; } return default(Symbol); symbols变量是类型为ListT的变量。ListT集合类型实现了IEnumerableT即可并且清晰地定义了一个 迭代器ListT的迭代器使用了一种结构体来实现。使用结构而不是类意味着通常可以避免任何在托管堆上的分配从而可以影响垃圾回收的效率。枚举典型的用处在于方便语言层面上使用foreach循环他使用enumerator结构体在调用推栈上返回。递增调用堆栈指针来为对象分配空间不会影响GC对托管对象的操作。
在上面的展开FirstOrDefault调用的例子中代码会调用IEnumeraboleT接口中的GetEnumerator()方法。将symbols赋值给IEnumerableSymbol类型的enumerable 变量会使得对象丢失了其实际的ListT类型信息。这就意味着当代码通过enumerable.GetEnumerator()方法获取迭代器时.NET Framework 必须对返回的值即迭代器使用结构体实现类型进行装箱从而将其赋给IEnumerableSymbol类型的(引用类型) enumerator变量。
解决方法
解决办法是重写FindMatchingSymbol方法将单个语句使用六行代码替代这些代码依旧连贯易于阅读和理解也很容易实现。 [js] view plaincopy public Symbol FindMatchingSymbol(string name) { foreach (Symbol s in symbols) { if (s.Name name) return s; } return null; } 代码中并没有使用LINQ扩展方法lambdas表达式和迭代器并且没有额外的内存分配开销。这是因为编译器看到symbol 是ListT类型的集合因为能够直接将返回的结构性的枚举器绑定到类型正确的本地变量上从而避免了对struct类型的装箱操作。原先的代码展示了C#语言丰富的表现形式以及.NET Framework 强大的生产力。该着后的代码则更加高效简单并没有添加复杂的代码而增加可维护性。
Aync异步
接下来的例子展示了当我们试图缓存一部方法返回值时的一个普遍问题
例6 缓存异步方法
Visual Studio IDE 的特性在很大程度上建立在新的C#和VB编译器获取语法树的基础上当编译器使用async的时候仍能够保持Visual Stuido能够响应。下面是获取语法树的第一个版本的代码 [js] view plaincopy class Parser { /*...*/ public SyntaxTree Syntax { get; } public Task ParseSourceCode() { /*...*/ } } class Compilation { /*...*/ public async TaskSyntaxTree GetSyntaxTreeAsync() { var parser new Parser(); // allocation await parser.ParseSourceCode(); // expensive return parser.Syntax; } } 可以看到调用GetSyntaxTreeAsync() 方法会实例化一个Parser对象解析代码然后返回一个TaskSyntaxTree对象。最耗性能的地方在为Parser实例分配内存并解析代码。方法中返回一个Task对象因此调用者可以await解析工作然后释放UI线程使得可以响应用户的输入。
由于Visual Studio的一些特性可能需要多次获取相同的语法树 所以通常可能会缓存解析结果来节省时间和内存分配但是下面的代码可能会导致内存分配 [js] view plaincopy class Compilation { /*...*/ private SyntaxTree cachedResult; public async TaskSyntaxTree GetSyntaxTreeAsync() { if (this.cachedResult null) { var parser new Parser(); // allocation await parser.ParseSourceCode(); // expensive this.cachedResult parser.Syntax; } return this.cachedResult; } } 代码中有一个SynataxTree类型的名为cachedResult的字段。当该字段为空的时候GetSyntaxTreeAsync()执行然后将结果保存在cache中。GetSyntaxTreeAsync()方法返回SyntaxTree对象。问题在于当有一个类型为TaskSyntaxTree 类型的async异步方法时想要返回SyntaxTree的值编译器会生出代码来分配一个Task来保存执行结果通过使用TaskSyntaxTree.FromResult()。Task会标记为完成然后结果立马返回。分配Task对象来存储执行的结果这个动作调用非常频繁因此修复该分配问题能够极大提高应用程序响应性。
解决方法
要移除保存完成了执行任务的分配可以缓存Task对象来保存完成的结果。 [js] view plaincopy class Compilation { /*...*/ private TaskSyntaxTree cachedResult; public TaskSyntaxTree GetSyntaxTreeAsync() { return this.cachedResult ?? (this.cachedResult GetSyntaxTreeUncachedAsync()); } private async TaskSyntaxTree GetSyntaxTreeUncachedAsync() { var parser new Parser(); // allocation await parser.ParseSourceCode(); // expensive return parser.Syntax; } } 代码将cachedResult 类型改为了TaskSyntaxTree 并且引入了async帮助函数来保存原始代码中的GetSyntaxTreeAsync()函数。GetSyntaxTreeAsync函数现在使用 null操作符来表示当cachedResult不为空时直接返回为空时GetSyntaxTreeAsync调用GetSyntaxTreeUncachedAsync()然后缓存结果。注意GetSyntaxTreeAsync并没有await调用GetSyntaxTreeUncachedAsync。没有使用await意味着当GetSyntaxTreeUncachedAsync返回Task类型时GetSyntaxTreeAsync 也立即返回Task 现在缓存的是Task因此在返回缓存结果的时候没有额外的内存分配。
其他一些影响性能的杂项
在大的app或者处理大量数据的App中还有几点可能会引发潜在的性能问题。
字典
在很多应用程序中Dictionary用的很广虽然字非常方便和高校但是经常会使用不当。在Visual Studio以及新的编译器中使用性能分析工具发现许多dictionay只包含有一个元素或者干脆是空的。一个空的Dictionay结构内部会有10个字段在x86机器上的托管堆上会占据48个字节。当需要在做映射或者关联数据结构需要事先常量时间查找的时候字典非常有用。但是当只有几个元素使用字典就会浪费大量内存空间。相反我们可以使用ListKeyValuePairK,V结构来实现便利对于少量元素来说同样高校。如果仅仅使用字典来加载数据然后读取数据那么使用一个具有Nlog(N)的查找效率的有序数组在速度上也会很快当然这些都取决于的元素的个数。
类和结构
不甚严格的讲在优化应用程序方面类和结构提供了一种经典的空间/时间的权衡(trade off)。在x86机器上每个类即使没有任何字段也会分配12 byte的空间 (译注来保存类型对象指针和同步索引块)但是将类作为方法之间参数传递的时候却十分高效廉价因为只需要传递指向类型实例的指针即可。结构体如果不撞向的话不会再托管堆上产生任何内存分配但是当将一个比较大的结构体作为方法参数或者返回值得时候需要CPU时间来自动复制和拷贝结构体然后将结构体的属性缓存到本地便两种以避免过多的数据拷贝。
缓存
性能优化的一个常用技巧是缓存结果。但是如果缓存没有大小上限或者良好的资源释放机制就会导致内存泄漏。在处理大数据量的时候如果在缓存中缓存了过多数据就会占用大量内存这样导致的垃圾回收开销就会超过在缓存中查找结果所带来的好处。
结论
在大的系统或者或者需要处理大量数据的系统中我们需要关注产生性能瓶颈症状这些问题再规模上会影响app的响应性如装箱操作、字符串操作、LINQ和Lambda表达式、缓存async方法、缓存缺少大小限制以及良好的资源释放策略、使用Dictionay不当、以及到处传递结构体等。在优化我们的应用程序的时候需要时刻注意之前提到过的四点
不要进行过早优化——在定位和发现问题之后再进行调优。专业测试不会说谎——没有评测便是猜测。好工具很重要。——下载 PerfView然后去看使用教程。内存分配决定app的响应性。——这也是新的编译器性能团队花的时间最多的地方。
参考资料
如果想观看关于这一话题的演讲可以在 Channel 9上观看。VS Profiler基础 http://msdn.microsoft.com/en-us/library/ms182372.aspx .NET 英语程序性能分析工具一览 http://msdn.microsoft.com/en-us/library/hh156536.aspx Windows Phone性能分析工具 http://msdn.microsoft.com/en-us/magazine/hh781024.aspx 一些C# 和VB性能优化建议 http://msdn.microsoft.com/en-us/library/ms173196(vvs.110).aspx(注:原文中该链接无内容连接地址应该使 http://msdn.microsoft.com/en-us/library/ms173196(vvs.100).aspx)一些高级优化建议 http://curah.microsoft.com/4604/improving-your-net-apps-startup-performance 文章出自
寒江独钓的博客