中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#性能優化實踐

發布時間:2020-06-27 12:01:12 來源:網絡 閱讀:1360 作者:powertoolsteam 欄目:編程語言

性能是考量一個控件產品好壞的重要指標,與產品的功能有著同等重要的地位。用戶在選擇一款控件產品的時候基本都會親身試驗比較同類產品的性能。作為選購那個控件重要因素之一。

控件的性能指什么

  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;

  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. Auts Performance Profiler
    Auts 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 &lt; gcMultiRow.RowCount; i++)
      {
          // Do something;
      }

      以上代碼一般情況下是沒有問題的,但是,如果GcMultiRow的行數比較大。而RowCount屬性的取值又比較慢的時候就需要使用緩存來做性能優化。

      int rowCount = gcMultiRow.RowCount; for (int i = 0; i &lt; rowCount; i++) {    // Do something; }
    2. 使用對象池也是一個常見的緩存方案,比使用Field或臨時變量稍微復雜一點。 例如,在MultiRow中,畫邊線,畫背景,需要用到大量的Brush和Pen。這些GDI對象每次用之前要創建,用完后要銷毀。創建和銷毀的過程是比較慢的。GcMultiRow使用的方案是創建一個GDIPool。本質上是一些Dictionary,使用顏色做Key。所以只有第一次取的時候需要創建,以后就直接使用以前創建好的。以下是GDIPool的代碼:

      public static class GDIPool
      {
          Dictionary&lt;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) =&gt;                {                    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. PostEvent 平臺本身也提供了一些異步方案。
        例如;在Winform下,觸發一塊區域重畫的時候,一般不適用Refresh而是Invalidate,這樣會觸發異步的刷新。在觸發之前可以多次Invalidate。BeginInvoke,PostMessage也都可以觸發異步的行為。
    7. 了解平臺特性
      如WPF的DP DP相對于CLR property來說是很慢的,包括Get和Set都很慢,這和一般質感上Get比較快Set比較慢不一樣。如果一個DP需要被多次讀取的話建議是CLR property做Cache。
    8. 進度條,提升用戶體驗
      有時候,以上提到的方案都沒有辦法快速響應用戶操作,進度條,一直轉圈圈的圖片,提示性文字如"你的操作可能需要較長時間請耐心等待"。都可以提升用戶體驗。可以作為最后方案來考慮。
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

平潭县| 循化| 噶尔县| 铁力市| 海兴县| 乐都县| 桐梓县| 恩施市| 株洲市| 连平县| 文登市| 虞城县| 上饶县| 隆尧县| 宝应县| 府谷县| 广宁县| 浦东新区| 玛纳斯县| 辰溪县| 中阳县| 龙口市| 昔阳县| 泸州市| 屯门区| 临海市| 元谋县| 本溪市| 龙海市| 壤塘县| 武山县| 绍兴县| 上思县| 盐亭县| 江安县| 玉龙| 花莲县| 亳州市| 潮州市| 荣昌县| 江川县|