C#品质优化实践

品质是考量一个软件出品上下的首要目标,与制品的功用有所同样紧要的地位。用户在拔取一款软件出品的时候基本都会亲自试验比较同类产品的质量。作为购买极度软件首要元素之一。

软件的特性指什么

  1. 下落内存消耗
    在软件开发中,内存消耗一般作为襄助的设想,因为现在的微机一般都享有比较大的内存,很多动静下,质量优化的手段就是空中换取时间。可是,并不是说,我们可以武断专行的挥霍内存。假使急需协理在大数据量的用例时,若是内存被耗尽,操作系统会生出高频的光景存沟通。导致执行进程急剧下跌。
  2. 进步执行进度
    1. 加载速度。
    2. 一定操作的响应速度。包含,点击,键盘输入,滚动,排序过滤等。

特性优化的口径

  1. 清楚必要
    以MultiRow产品为例,MultiRow的一个质量必要是:”百万行数据绑定下平滑滚动。”整个MultiRow项目标开发进度一直要考虑那几个目标。
  2. 明白瓶颈
    据悉经验,99%的习性消耗是由于1%的代码造成的。所以,超过一半特性优化都是针对性那1%的瓶颈代码举办的。具体实施也就分为两步。首先,确定瓶颈,其次消除瓶颈。
  3. 切忌过分
    先是必要求认识到,性能优化自己是有花费的。那么些资金不单单浮现在做品质优化所提交的工作量。还包含为品质优化而写出的纷纭代码,额外的护卫费用,会引入新的Bug,额外的内存开支等。
    一个普遍难点是,一些刚接触软件开发的同学会对有些不须求的点一步一趋品质优化技术仍然设计情势,带来不必要的复杂度。品质优化日常须要对低收入和资本之间做出权衡。

什么发现品质瓶颈

上一节提到,品质优化的第一步就是意识品质瓶颈,这一节关键介绍定位品质瓶颈的有些履行。

  1. 如何获取内存消耗
    以下代码可以收获某个操作的内存消耗。

    // 在这里写一些可能消耗内存的代码,例如,如果想了解创建一个GcMultiRow软件需要多少内存可以执行以下代码
    
    long start = GC.GetTotalMemory(true);
    
    var gcMulitRow1 = new GcMultiRow();
    
    GC.Collect();
    
    // 确保所有内存都被GC回收
    
    GC.WaitForFullGCComplete();
    
    long end = GC.GetTotalMemory(true);
    
    long useMemory = end - start; 
    

    C#,  

  2. 怎么赢得时间消耗
    以下代码可以赢得某个操作时间消耗。

    System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
    watch.Start();
    for (int i = 0; i < 1000; i++)
    {
         gcMultiRow1.Sort();
    }
    watch.Stop();
    var useTime = (double)watch.ElapsedMilliseconds / 1000; 
    

     

    此处把一个操作循环执行了1000次,最后再把消耗的时辰除以1000来确定最后消耗的时刻。可以是结果更精确稳定,排除意外数据。

  3. 由此CodeReview发现质量难题。
    许多状态下,可以由此CodeReview发现质量难题。对于大数据量的循环,要丰富关切。循环内的逻辑应该履行的尽量的快。

  4. ANTS Performance Profiler
    ANTS Profiler是款效率强大的习性检测软件。可以很好的援救大家发现质量瓶颈。使用那款软件定位品质瓶颈可以起到一石多鸟的效果。熟习使用那一个工具,大家可以便捷准确的永恒到有总体性难题的代码。
    那些工具很有力,不过也并不是完美无缺的。首先,那是一款收费软件,部门唯有几个许可号。其次,这么些软件的干活原理是在IL中参预一些钩子,用来记录时间。所以在条分缕析时,软件的履行进度会比其实运行慢一些得到的数额也就此并不是任何的准确,应该把软件分析的多寡作为参照,支持急速定位难题,不过并非完全依靠,还要结合其他技术来分析程序的属性。

 

属性优化的措施和技艺

定位了质量难题后,解决的形式有好多。这些章节会介绍一些品质优化的技术和执行。

  1. 优化程序结构
    对于程序结构,在筹划时就相应考虑,评估是还是不是可以高达质量要求。假如前期发现了质量难点亟待考虑调整结构会牵动相当大的支付。举例:

    1. GcMultiRowGcMultiRow要协理100万行数据,假若每行有10列的话,就要求有1000万个单元格,每个单元格上又有那一个的属性。借使不做其他优化的话,大数据量时,一个GcMultiRow软件的内存费用会分外的大。GcMultiRow接纳的方案是应用哈希表来存储行数据。唯有用户改过的行放到哈希表里,而对于多数并未改过的行都直接利用模板代替。就高达了节约内存的目的。
    2. Spread for WPF/Silverlight
      (SSL)WPF的画法和Winform不一致,是由此整合View元素的方法已毕的。SSL同样协理百万级的数据量,可是又无法给每个单元格都分配一个View。所以SSL使用了VirtualizePanel来兑现画法。思路是每一个View是一个Cell的浮现模块。可以和Cell的多少模块分离。那样。只须求为浮现出来的Cell创建View。当暴发滚动时会有部分Cell滚出屏幕,有局地Cell滚入屏幕。那时,让滚出屏幕的Cell和View分离。然后再复用那部分View给新进入屏幕的Cell。如此循环往复。那样只须求几百个View就可以扶助广大的Cell。
  2. 缓存
    缓存(Cache)是性质优化中最常用的优化手段.适用的状态是几度的得到一些数额,而每一遍得到这么些数量需求的时间相比较长。那时,第二回拿走的时候会用正常的法子,并且在取得之后把数量缓存下来。之后就应用缓存的多少。
    若是利用了缓存的优化措施,须要更加注意缓存数据的一块,就是说,假若实在的数量暴发了变化,应该登时的排除缓存数据,确保不会因为缓存而利用了错误的数码。
    举例:

    1. 利用缓存的状态比较多。最简单易行的情状就是缓存到一个Field或临时变量里。

      for(int i = 0; i < gcMultiRow.RowCount; i++) 
      { 
          // Do something; 
      } 
      
      以上代码一般情况下是没有问题的,但是,如果GcMultiRow的行数比较大。而RowCount属性的取值又比较慢的时候就需要使用缓存来做性能优化。            
      
      int rowCount = gcMultiRow.RowCount;
      for (int i = 0; i < rowCount; i++)
      {
         // Do something;
      }
      

        

    2. 利用对象池也是一个科普的缓存方案,比选用Field或临时变量稍微复杂一点。
      例如,在MultiRow中,画边线,画背景,必要用到大方的Brush和Pen。这么些GDI对象每便用事先要创建,用完后要销毁。创造和销毁的经过是相比慢的。GcMultiRow使用的方案是创办一个GDIPool。本质上是有些Dictionary,使用颜色做Key。所以唯有首先次取的时候须求创建,将来就径直行使此前成立好的。以下是GDIPool的代码:

      public static class GDIPool 
      { 
          Dictionary<Color, Brush > _cacheBrush = new Dictionary<Color, Brush>(); 
          Dictionary<Color, Pen> _cachePen = new Dictionary<Color, Pen>(); 
          public static Pen GetPen(Color color) 
         { 
             Pen pen; 
             if_cachePen.TryGetValue(color, out pen)) 
             { 
                 return pen; 
             } 
             pen = new Pen(color); 
            _cachePen.Add(color, pen); 
             return pen; 
         } 
      }
      

        

    3. 懒构造
      有时候,有的对象制造需求开销较长期。而以此目的可能并不是有着的风貌下都须求使用。那时,使用赖构造的章程可以有效进步质量。
      举例:对象A必要中间创制对象B。对象B的布局时间相比长。
      一般做法:

      public class A
      {
         public B _b = new B();
      }
      

        

      诚如做法下是因为协会对象A的同时要布局对象B导致了A的构造速度也变慢了。优化做法:

      public class A
      {
         private B _b;
         public B BProperty
         {
             get
            {
               if(_b == null)
               {
                   _b = new B();
               }
               return _b;
            }
         }
      }
      

        

      优化后,构造A的时候就不需要创建B对象,只有需要使用的时候才需要构造B对象。
      
    4. 优化算法
      优化算法能够有效的升高一定操作的质量,使用一种算法时应有通晓算法的适用意况,最好状态和最坏情形。
      以GcMultiRow为例,最初MultiRow的排序算法使用了经典的迅猛排序算法。这看起来是从未有过难点的,可是,对于表格软件,用户时时的操作是对有序表展开排序,如顺序和倒序之间切换。而经典的便捷排序算法的最差意况就是骨干平稳的事态。所以经典快捷排序算法不合乎MultiRow。最后经过改的排序算法解决了那些题材。立异的长足排序算法使用了3个焦点来代替经典快排的一个当中的算法。每趟沟通都是从3个要旨中精选一个。那样,乱序和中坚有序的动静都不是以此算法的最坏情状,从而优化了质量。

    5. 叩问Framework提供的数据结构
      俺们现在工作的.net
      framework平台,有很多现成的多寡数据结构。我们相应领悟这一个数据结构,进步大家先后的质量:
      举例:

      1. string 的加运算符 VS StringBuilder:
        字符串的操作是大家平时蒙受的基本操作之一。
        大家平时会写这么的代码 string str = str1 +
        str2。当操作的字符串很少的时候,那样的操作小难点。然则倘诺大气操作的时候(例如文本文件的Save/Load,
        Asp.net的Render),那样做就会牵动严重的性质难点。那时,咱们就应当用StringBuilder来代替string的加操作。
      2. Dictionary VS List
        Dictionary和List是最常用的三种集合类。选用正确的集合类可以很大的晋级程序的属性。为了做出科学的选择,大家应有对Dictionary和List的各样操作的性质相比较驾驭。
        下表中概括的列出了三种数据结构的习性相比。

        操作

        List

        Dictionary

        索引

        Find(Contains)

        Add

        Insert

        Remove

      3. TryGetValue
        对于Dictionary的取值,相比较一向的主意是之类代码:

        if(_dic.ContainKey("Key")
        {
            return _dic\["Key"\];
        }
        

          

        当需要多量取值的时候,那样的取法会带来质量难点。优化措施如下:

        object value;
        if(_dic.TryGetValue("Key", out value))
        {
            return value;
        }
        

          

        动用TryGetValue可以比先Contain再取值提升一倍的特性。

      4. 为Dictionary拔取恰当的Key。
        Dictionary的取值质量很大状态下取决于做Key的靶子的Equals和GetHashCode五个章程的品质。若是可以的话使用Int做Key质量最好。即便是一个自定义的Class做Key的话,最好有限协理以下两点:1.
        见仁见智对象的GetHashCode重复率低。2.
        GetHashCode和Equals方法登时简单,效用高。

      5. List的Sort和BinarySearch品质很好,倘若能满足成效需要的话推荐直接运用,而不是友好重写。

        List<int> list = new List<int>{3, 10, 15};
        list.BinarySearch(10); // 对于存在的值,结果是1
        list.BinarySearch(8); // 对于不存在的值,会使用负数表示位置,如查找8时,结果是-2, 查找0结果是-1,查找100结果是-4.
        

          

    6. 经过异步进步响应时间

      1. 多线程
        多少操作确实需求花费相比较长的时日,如若用户的操作在那段时间卡死会带来很差的用户体验。有时候,使用二十四线程技术可以缓解这些难点举例:
        CalculatorEngine在布局的时候要初阶化所有的Function。由于Function相比多,开始化时间会比较长。那是就用到了三四线程技术,在办事线程中做Function的伊始化工作,就不影响主线程快速响应用户的此外操作了。代码如下:

        public CalcParser()
        {
           if (_functions == null)
           {
               lock (_obtainFunctionLocker)
               {
                   if (_functions == null)
                   {
                       System.Threading.ThreadPool.QueueUserWorkItem((s) =>
                       {
                           if (_functions == null)
                           {
                               lock (_obtainFunctionLocker)
                               {
                                   if (_functions == null)
                                   {
                                       _functions = EnsureFunctions();
                                   }
                               }
                           }
                       });
                   }
               }
           }
        } 
        

          

        此处相比较慢的操作就是EnsureFunctions函数,是在另一个线程里推行的,不会影响主线程的响应。当然,使用二十四线程是一个相比有难度的方案,须求丰富考虑跨线程访问和死锁的标题。

      2. 加延迟时间
        在GcMultiRow兑现AutoFilter功效的时候利用了一个好像于推迟执行的方案来提高响应速度。AutoFilter的成效是用户在输入的长河中根据用户的输入更新筛选的结果。数据量大的时候两回筛选需求较长期,会影响用户的一而再输入。使用多线或者是个好的方案,然而利用多线程会增多程序的复杂度。MultiRow的缓解方案是当收到到用户的键盘输入音讯的时候,并不立刻出发Filter,而是等待0.3秒。若是用户在连接输入,会在那0.3秒内再也接受键盘音讯,就再等0.3秒。直到三番五次0.3秒内尚未新的键盘信息时再触发Filter。有限支撑了神速响应用户输入的目的。

      3. Application.Idle事件
        在GcMultiRow的Designer里,平时要基于当下的处境刷新ToolBar上按钮的Disable/Enable状态。三次刷新须求较长的年月。假诺用户延续输入会有卡顿的觉得,影响用户体验。GcMultiRow的优化方案是挂系统的Application.Idle事件。当系统空闲的时候,系统会接触那个事件。接到那么些事件代表那时用户已经达成了连接的输入,那时就足以从容的刷新按钮的图景了。
      4. Invalidate, BeginInvoke. Post伊芙nt
        平台我也提供了有的异步方案。
        诸如;在Winform下,触发一块区域重画的时候,一般不适用Refresh而是Invalidate,那样会触发异步的刷新。在接触此前可以屡屡Invalidate。BeginInvoke,PostMessage也都得以触发异步的作为。
    7. 摸底平台特色
      如WPF的DP DP绝对于CLR
      property来说是很慢的,包罗Get和Set都很慢,那和一般质感上Get相比较快Set相比慢分裂。若是一个DP必要被频仍读取的话指出是CLR
      property做Cache。

    8. 进程条,提高用户体验
      有时候,以上关联的方案都没有艺术神速响应用户操作,进程条,平素转圈圈的图形,提醒性文字如”你的操作可能须求较长期请耐心等待”。都足以升官用户体验。可以视作最后方案来设想。

相关文章