<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      .NET 的 Debug 和 Release build 對(duì)執(zhí)行速度的影響

      這篇文章發(fā)布于我的 github 博客:原文

      在真正開(kāi)始討論之前先定義一下 Scope。

      • 本文討論的范圍限于執(zhí)行速度,內(nèi)存占用什么的不在評(píng)估的范圍之內(nèi)。
      • 本文不討論算法:編譯器帶來(lái)的優(yōu)化基本上屬于底層的優(yōu)化,難以從質(zhì)上提升執(zhí)行速度。程序的快慢主要影響因素是采用的數(shù)據(jù)結(jié)構(gòu)和算法這些高層次上的東西。我們接下來(lái)的討論建立在這些高層次的東西已經(jīng)被充分考慮的基礎(chǔ)之上。

      目錄

      • .NET 的 Debug 和 Release build 對(duì)執(zhí)行速度的影響
        • 如果你沒(méi)有時(shí)間
        • Debug 和 Release build 的主要差異
        • 觀察 JIT 優(yōu)化的代碼
        • JIT 優(yōu)化對(duì)不同場(chǎng)景的影響
          • 迭代和內(nèi)存操作
          • 非頻繁的庫(kù)調(diào)用
          • 頻繁的庫(kù)調(diào)用
          • 頻繁的閉包調(diào)用
        • 結(jié)論

      如果你沒(méi)有時(shí)間

      那么答案是:

      • 對(duì)執(zhí)行速度有影響。影響主要是由 JIT 而非 IL 編譯器引入的。
      • 一般來(lái)說(shuō) Release build 使得本程序集內(nèi)的代碼執(zhí)行速度更快,而對(duì)第三方庫(kù)的調(diào)用則幾乎沒(méi)有影響。
      • 對(duì)于本程序集內(nèi)代碼來(lái)說(shuō),Release build 對(duì)迭代和內(nèi)存操作加速效果比較明顯。

      Debug 和 Release build 的主要差異

      Debug 和 Release build 的一個(gè)最主要的區(qū)別是 Release build 會(huì)添加 /optimize 選項(xiàng)。這個(gè)選項(xiàng)完成了兩個(gè)任務(wù):優(yōu)化 IL 代碼以及添加元數(shù)據(jù)。有意思的是第一項(xiàng)對(duì)性能提升的影響并不大。這是因?yàn)?CLR 應(yīng)用的性能提升主要是由于 JIT 編譯器而非具體的語(yǔ)言編譯器完成的。語(yǔ)言編譯器所完成的優(yōu)化是很有限的,例如(不限于下面這些):

      • 當(dāng)一個(gè)表達(dá)式的邏輯僅僅有一個(gè)作用而無(wú)其他副作用的時(shí)候?qū)⒅粫?huì)把產(chǎn)生這些副作用的代碼進(jìn)行生成;
      • 忽略無(wú)用的賦值。例如 int foo = 0。因?yàn)槲覀冎?memory allocator 會(huì)將其初始化為 0(注:這是 IL 一級(jí)而非語(yǔ)言一級(jí)的);
      • 當(dāng)靜態(tài)類(lèi)沒(méi)有需要初始化的 field,或者 field 初始化為默認(rèn)值時(shí)忽略對(duì)靜態(tài)構(gòu)造函數(shù)的生成;
      • 忽略迭代中的局部未引用的變量(包括在僅僅在閉包的迭代中的未引用的外部變量);
      • 復(fù)用函數(shù)的棧空間(局部變量復(fù)用,刪除未使用的局部變量);
      • 減少對(duì)局部變量(例如 ifswitch 表達(dá)式的結(jié)果,以及函數(shù)調(diào)用的返回值)存儲(chǔ)的要求而盡量的使用棧空間;
      • 優(yōu)化 branch 跳轉(zhuǎn)指令;

      這些優(yōu)化都非常的直接,如果查看程序集的 IL 語(yǔ)句,會(huì)發(fā)現(xiàn) /optimize 打開(kāi)或關(guān)閉的情況下生成的代碼幾乎是相同的。不會(huì)有 IL 內(nèi)聯(lián)優(yōu)化和循環(huán)的展開(kāi)這種高級(jí)優(yōu)化。因此性能提升不大。

      但是,有一條 IL 優(yōu)化對(duì)性能提升做出了相當(dāng)?shù)呢暙I(xiàn),這里特別介紹一下:

      • 刪除了一部分為 breakpoints 定位以及 edit and continue 而插入的 nop 指令,若知詳情如何,請(qǐng)看下面的 Tips。

      真正起做用的是 /optimize 選項(xiàng)的第二個(gè)任務(wù),更改 DebuggableAttribute 的參數(shù)。在添加 /optmize 的情況下該參數(shù)為 IgnoreSymbolStoreSequencePoints,而不會(huì)包含 DisableOptimizationsEnableEditAndContinue。這會(huì)實(shí)際的影響 JIT 編譯器生成代碼的策略。

      Tip:

      MSDN 對(duì) IgnoreSymbolStoreSequencePoints 解釋為:使用 MSIL 的序列點(diǎn)而非 PDB 的序列點(diǎn)。JIT 編譯器不會(huì)將兩個(gè)序列點(diǎn)的進(jìn)行合并優(yōu)化編譯,因而使用 PDB 文件中提供的序列點(diǎn)就可以保證編譯結(jié)果和 PDB 嚴(yán)格對(duì)應(yīng)從而提供更好的 Debug 體驗(yàn)。

      有的同學(xué)就開(kāi)始激動(dòng)了,那么我如果使用 Debug Build 但是將 PDB 刪除是否能夠有和 optimize 一樣的性能呢?顯然不是的!由于加載 PDB 引發(fā)的這種性能問(wèn)題在 .NET 2.0 的時(shí)候就已經(jīng)解決了。解決的方式就是在 Debug build 中添加了 nop 指令作為隱式的序列點(diǎn)。從那之后,即使在 Debug build 下,其 DebuggingModes 也會(huì)包含 IgnoreSymbolStoreSequencePoints 選項(xiàng)了(因?yàn)楦緵](méi)有必要加載 PDB)。

      此時(shí)應(yīng)該明白了為何在 Release build 下刪除了一些 nop 指令會(huì)使得執(zhí)行效率得到提升,因?yàn)閯h除 nop 指令使得 JIT 編譯器可以在相對(duì)大的范圍內(nèi)進(jìn)行代碼優(yōu)化。

      但是如果入口應(yīng)用程序是 Debug build 會(huì)不會(huì)影響 .NET BCL 或者第三方庫(kù)的 JIT 編譯結(jié)果呢?這是不會(huì)的,因?yàn)檫@個(gè)屬性是 assembly scope 的,只要你使用的是 optimize 過(guò)的第三方庫(kù)都會(huì)得到優(yōu)化的 JIT 代碼。

      觀察 JIT 優(yōu)化的代碼

      本文不會(huì)廣泛展示 JIT 優(yōu)化的結(jié)果,但是如果你希望對(duì)比一下 Debug 和 Release build 下的 JIT 編譯結(jié)果必須首先更改 Visual Studio 的默認(rèn) Debug 設(shè)置。

      在 Visual Studio 中選擇 Tools -> Options -> Debugging -> General。

      • 取消 Enable Just My Code(這是因?yàn)閮?yōu)化的代碼不屬于 My Code 的范疇)
      • 取消 Suppress JIT optimization on module load (Managed only)(防止在 Visual Studio 啟動(dòng)項(xiàng)目時(shí)阻止 JIT 優(yōu)化)

      至此就可以使用 Visual Studio 的調(diào)試器,在斷點(diǎn)命中時(shí)通過(guò) disassembly 窗口觀看優(yōu)化后的匯編代碼了。

      JIT 優(yōu)化對(duì)不同場(chǎng)景的影響

      即便我們認(rèn)識(shí)到開(kāi)啟 optimize 有可能使執(zhí)行速度得到提升,但是在不同的使用場(chǎng)景下,其提升效果是不同的。

      迭代和內(nèi)存操作

      場(chǎng)景之一是自己的代碼中包含比較多的算法成分(并不是調(diào)用系統(tǒng)或者第三方庫(kù)的算法而是自己實(shí)現(xiàn)算法)。算法中最典型的即極多的迭代操作和內(nèi)存讀寫(xiě),因而我們選擇插入排序作為測(cè)試算法。

      // sample code
      int length = collection.Count;
      for (int outerIndex = 0; outerIndex < length; ++outerIndex)
      {
          int minimumIndex = outerIndex;
          T minimum = collection[outerIndex];
          for (int innerIndex = outerIndex + 1; 
              innerIndex < length; 
              ++innerIndex)
          {
              if (collection[innerIndex].CompareTo(minimum) >= 0) 
              {
                  continue;
              }
              
              minimumIndex = innerIndex;
              minimum = collection[innerIndex];
          }
      
          Utility.Swap(collection, outerIndex, minimumIndex);
      }
      

      測(cè)試結(jié)果如下:

      Iteration on value type test (selection sort on 20000 32-bit int array)

      • Debug build: 4.56s
      • Release build: 1.81s

      我們必須確認(rèn)兩種不同的 build 的執(zhí)行速度提升確實(shí)發(fā)生在迭代和內(nèi)存讀寫(xiě)上。通過(guò) Profiling 我們可以證實(shí)這一猜想。其性能提升主要發(fā)生在循環(huán)體迭代,也就是 for (int outerIndex = 0; outerIndex < length; ++outerIndex),數(shù)組數(shù)據(jù)讀寫(xiě),以及細(xì)小方法調(diào)用 collection[innerIndex].CompareTo(minimum) 上。其優(yōu)化手法主要是盡量使用寄存器而不是內(nèi)存尋址。

      例如,內(nèi)層循環(huán) for (int innerIndex = outerIndex + 1; innerIndex < length; ++innerIndex) 在 Release build 下被編譯為:

      // outerIndex + 1
      00007FFCBA5746F2  inc         ebx
      // stack pointer change
      00007FFCBA5746F4  inc         ebp 
      // compare innerIndex to length
      00007FFCBA5746F6  cmp         ebx,esi
      00007FFCBA5746F8  jl          00007FFCBA5746A0
      

      而 Debug build 是這樣的

      // read outerIndex to eax, increase eax then stores the value back
      00007FFCBA594BA5  mov         eax,dword ptr [rbp+7Ch]  
      00007FFCBA594BA8  inc         eax
      00007FFCBA594BAA  mov         dword ptr [rbp+7Ch],eax
      // set ecx to 0
      00007FFCBA594BAD  xor         ecx,ecx  
      // load length to eax
      00007FFCBA594BAF  mov         eax,dword ptr [rbp+8Ch]  
      // compare with increased outerIndex and to set the flag, move the flag value to eax and test if the value is true or not
      00007FFCBA594BB5  cmp         dword ptr [rbp+7Ch],eax  
      00007FFCBA594BB8  setl        cl  
      00007FFCBA594BBB  mov         dword ptr [rbp+64h],ecx  
      00007FFCBA594BBE  movzx       eax,byte ptr [rbp+64h]  
      00007FFCBA594BC2  mov         byte ptr [rbp+77h],al  
      00007FFCBA594BC5  movzx       eax,byte ptr [rbp+77h]  
      00007FFCBA594BC9  test        eax,eax  
      00007FFCBA594BCB  jne         00007FFCBA594B04  
      

      JIT 還將 int.CompareTo 的調(diào)用進(jìn)行了內(nèi)聯(lián)。在本例中,其貢獻(xiàn)達(dá)到了 50% 左右,但是這個(gè)提升只在所有操作都基本是細(xì)小操作的時(shí)候才會(huì)顯現(xiàn)。

      從上述分析中不難看出,/optimize 對(duì)迭代中的內(nèi)存操作的優(yōu)化非常有效,因此如果我們迭代的并非 value type 而是需要多次進(jìn)行尋址(因?yàn)橐粩嗟氖褂闷?field 值)的 reference type 則性能提升也會(huì)非常明顯。

      Iteration on reference type test (selection sort on 20000 ref instance array. The ref type contains 1 int field)

      • Debug build: 11.57s
      • Release build: 4.00s

      類(lèi)似的操作還例如 DTO 之間的映射,這個(gè)操作也屬于迭代式的內(nèi)存密集形操作。在如下的測(cè)試代碼:

      var source = Enumerable.Range(0, dataAmount)
          .Select(
              i => new Dto
              {
                  Name = new NameDto
                  {
                      FirstName = "firstname" + i,
                      Middle = "Q",
                      LastName = "lastname" + i
                  },
                  Age = 20 + i % 10
              });
      
      var destination = source.Select(
          e => new
          {
              FirstName = e.Name.FirstName,
              MiddleName = e.Name.Middle,
              LastName = e.Name.LastName,
              Age = e.Age
          });
      
      m_count = destination.Count();
      

      每一次 5,000,000 個(gè)迭代測(cè)試的情況下能夠獲得 15% 以上的執(zhí)行速度提升。其主要的優(yōu)化手段仍然是盡量的使用寄存器。

      非頻繁的庫(kù)調(diào)用

      該場(chǎng)景下僅對(duì)系統(tǒng)或者第三方庫(kù)進(jìn)行非頻繁調(diào)用。非頻繁的調(diào)用有兩種情況,第一種情況屬于調(diào)用的方法仍是有相當(dāng)復(fù)雜程度的算法,這是非頻繁調(diào)用的常見(jiàn)情況;第二種是非頻繁調(diào)用的方法也非常簡(jiǎn)單,但是這對(duì)性能影響不大因此我們只關(guān)注第一種情況。

      在測(cè)試之前,不妨預(yù)測(cè)一下,由于我們系統(tǒng) BCL 和第三方庫(kù)均使用 /optimize 進(jìn)行 build,因此對(duì)于非頻繁的庫(kù)調(diào)用,我們的代碼優(yōu)化的空間并不大,性能數(shù)據(jù)應(yīng)當(dāng)非常接近。以下是測(cè)試結(jié)果。

      Infrequent lib calls (quick sort 9,000,000 integers x 5 runs)

      • Debug build: 6.37s
      • Release build: 6.62s

      頻繁的庫(kù)調(diào)用

      頻繁的庫(kù)調(diào)用往往包含對(duì)細(xì)小的操作進(jìn)行的調(diào)用。我們著重關(guān)注 Parsing 和 ToString 這兩種常見(jiàn)的操作。這是因?yàn)樵?Web App 中,Serialize - deserialize 是最頻繁而常見(jiàn)的操作。

      同樣我們可以預(yù)測(cè)執(zhí)行的結(jié)果。由于是頻繁操作,因此迭代部分的性能會(huì)有一些增強(qiáng)。但是相比于迭代,庫(kù)調(diào)用的時(shí)間要長(zhǎng)的多,因此這種性能增益幾乎是不可見(jiàn)的。可以預(yù)見(jiàn)其性能數(shù)據(jù)應(yīng)當(dāng)是非常接近的。

      測(cè)試代碼范例:

      double next = 1d + random.NextDouble();
      total += double.Parse(next.ToString(CultureInfo.InvariantCulture));
      

      Frequent lib calls (serialize/deserialize double x 5,000,000 times)

      • Debug build: 6.89s
      • Release build: 6.77s

      為了保證測(cè)試的有效性我們?nèi)匀恍枰_認(rèn)性能的消耗主要發(fā)生在 serialize - deserialize 上。Profile 結(jié)果和我們預(yù)想是一致的:

      for (int i = 0; i < iterationCount; ++i) // 0.5%
      {
          double next = 1d + random.NextDouble(); // 1.3%
          total += double.Parse(
              next.ToString(CultureInfo.InvariantCulture)); // 98.2%
      }
      

      頻繁的閉包調(diào)用

      我們關(guān)注頻繁的閉包調(diào)用,因?yàn)?LINQ 以及事件處理已經(jīng)得到了非常廣泛的應(yīng)用。其典型形式是使用匿名函數(shù)或 lambda 表達(dá)式作為回調(diào)方法。回調(diào)方法往往執(zhí)行數(shù)據(jù)的加工(Select)或者篩選(Where)。

      由于使用 LINQ 就是庫(kù)調(diào)用,因此迭代的優(yōu)化不論 Debug 還是 Release build 都會(huì)發(fā)生,唯一的優(yōu)化空間只是匿名委托的內(nèi)聯(lián)以及寄存器的使用,但這樣也不會(huì)帶來(lái)什么性能提升,因?yàn)榇蠖鄶?shù)情況下匿名函數(shù)的執(zhí)行時(shí)間要比 call 長(zhǎng)的多。可以預(yù)見(jiàn),Debug build 和 Release build 的性能指標(biāo)是比較接近的。

      測(cè)試代碼范例:

      IEnumerable<double> enumerable = Enumerable
          .Range(1, iterationAmount)
          .Select(
              i =>
              {
                  string str = i.ToString(CultureInfo.InvariantCulture);
                  int operand = int.Parse(str);
                  return Math.Pow(operand, factor);
              })
          .Where(i => i > 0.2);
      double total = enumerable.Average();
      

      Frequent closure calls (for 8,000,000 iterations)

      • Debug build: 4.51s
      • Release build: 4.47s

      結(jié)論

      可見(jiàn) JIT 編譯器對(duì) BCL 以及 Release build 下的第三方庫(kù)調(diào)用影響并不大,因?yàn)楸镜卮a本身并不占有很多的比重,典型的情形例如數(shù)據(jù)庫(kù)查詢(xún)。但是對(duì)于本地代碼占有很高比重,且其中包含大量的迭代和內(nèi)存操作的情形(光線(xiàn)追蹤,服務(wù)端頁(yè)面生成(非預(yù)編譯的情形),批量 DTO / Entity 映射)的可以起到比較不錯(cuò)的優(yōu)化效果。

      因此,從執(zhí)行速度的角度上考慮,推薦在 Package/Deployment 的時(shí)候切換至 Release build。

      posted @ 2015-08-28 23:13  TW-劉夏  閱讀(7805)  評(píng)論(10)    收藏  舉報(bào)
      主站蜘蛛池模板: 加勒比中文字幕无码一区| 亚洲伊人精品久视频国产| 午夜精品福利亚洲国产| 亚洲v欧美v日韩v国产v| 亚洲热无码av一区二区东京热av| 99精品国产综合久久久久五月天 | 亚洲加勒比久久88色综合| 免费国产精品黄色一区二区| 男女猛烈无遮挡免费视频APP| 成人精品自拍视频免费看| 国精产品自偷自偷ym使用方法| 人人爽亚洲aⅴ人人爽av人人片| 无码人妻丰满熟妇奶水区码| 国产精品三级黄色小视频| 免费无码高潮流白浆视频| 亚洲精国产一区二区三区| 欧美成人精品手机在线| 亚洲熟妇自偷自拍另类| 久久亚洲日本激情战少妇| 欧美高清一区三区在线专区| 国产一区二区亚洲一区二区三区| 国内少妇人妻丰满av| 国产成人午夜福利在线播放| 国产日产亚洲系列最新| 亚洲国产欧美在线看片一国产 | 人妻体体内射精一区二区| 中文字幕精品人妻丝袜| 午夜成人精品福利网站在线观看| 性奴sm虐辱暴力视频网站| 人妻有码av中文字幕久久琪| 在线看无码的免费网站| 人妻系列中文字幕精品| 国产成人精品三级在线影院| 男人狂桶女人出白浆免费视频 | 亚洲国产亚洲国产路线久久| 亚洲精品一区二区三区四区乱码| 国产不卡在线一区二区| 无码av天天av天天爽| 国产午夜精品一区理论片| 人妻中文字幕亚洲精品| 国产精品国产高清国产专区|