YACEP相關技術工具服務技巧(上)

方外老和尚發表於2019-05-27

    這篇隨筆的核心是介紹一下YACEP所用到的一些技術工具,服務和技巧,鑑於篇幅原因,不可能面面俱到,只能點到為止,目錄如下:


    

    目錄:

          1. YACEP簡介(上)

            2. 技術篇(上)

               2.1 利用優先爬山演算法解決運算子優先順序的問題

               2.2 利用ReadOnlySpan加速字串解析

               2.3 利用表示式樹和Emit生成表示式執行代理

           3. 工具篇(上)

                3.1 測試覆蓋率工具 - Coverlet 

                3.2 覆蓋率報表轉換工具 - ReportGenerator 

                3.3 基準測試工具 - BenchmarkDotNet

           4. 服務篇(下)

                4.1  測試覆蓋率服務 - Codecov

                4.2 程式碼質量分析服務 - SonarCloud

                4.3 持續整合服務1 - Travis CI

                4.4 持續整合服務2 - AppVeyor

                4.5 持續整合服務3 - Azure DevOps

           5. 技巧篇(下)

               5.1 利用props檔案抽離csproj的公共配置

               5.2 利用WSL跨平臺測試程式碼

               5.3 利用持續整合服務檢查PR

               5.4 更易寫的文件格式 - AsciiDoc

               5.5 如何給你的專案新增更多的徽章

               5.6 利用git message自動釋出NuGet包

 


1. YACEP  簡介

     YACEP : yet another csharp expression parser,是一款基於netstandard2.0構建的輕量級高效能表示式解析器。能夠將一段有效的字串並轉換成一棵抽象語法樹,同時可以把抽象語法樹轉換成一段可以執行的程式碼。

      專案使用MIT開源協議,程式碼託管在GitHub上,更多更詳細的資訊可以去看官方文件,隨便給個star什麼的就再好不過了:)

      YACEP  的核心是一個輕量級的表示式解析器,其解析程式碼不到500行,算上輔助的一些操作,整個解析器程式碼不到600行。解析器內部使用了  ReadOnlySpan 來處理字串,所以在做長串處理時,記憶體消耗可以做到很低,處理起來也非常快。

     YACEP  還附加實現了一個簡單編譯器,可以將抽象語法樹轉換成可執行程式碼。編譯器的介面申明瞭兩個方法,一個不帶泛型引數,一個帶了泛型引數。所以在 YACEP  的內部編譯器的實現有兩個,第一個是不做執行時型別繫結的實現,一個是限定執行時型別繫結的實現。前者有更好的靈活性,有類似Python這種語言的動態能力,所以無法在編譯完成時生成更優化的IL指令,效能一般。而後者是在編譯時就已經限定了具體的型別,所以能夠生成更短的IL指令,效能相對於第一種有非常大的提升。

1  public interface ICompiler
2     {
3         ///不做執行時型別繫結
4         IEvaluator Compile(EvaluableExpression expression);
5         ///做執行時型別繫結
6         IEvaluator<TState> Compile<TState>(EvaluableExpression expression);
7     }

      YACEP  具體的其它特性可以去 GitHub 上檢視,這裡就直接從官方複製過來,如下:

  • 開箱即用,內建了的字面值, 一元及二元操作符以及統計類與時間類函式可滿足大部分使用場景
  • 跨平臺,基於netstandard2.0標準構建
  • 輕量級,只有500多行程式碼實現的輕量級詞法分析器
  • 低消耗,詞法分析器使用 ReadOnlySpan 解析字串
  • 高效能,使用EMIT技術生成IL來構建可執行物件(檢視基準測試報告)
  • 支援條件表示式
  • 支援索引器
  • 支援 in 表示式
  • 支援自定義字面量
  • 支援自定義一元操作符
  • 支援自定義二元操作符
  • 支援自定義函式 

2.技術篇

2.1 利用優先爬山演算法解決運算子優先順序的問題

          優先爬山演算法是一種深度優先的演算法,它採用啟發式的方式進行區域性擇優。現在的很多人工智慧技術也有用到此演算法。(後簡稱爬山演算法)

          更詳細的介紹請點選此連結:https://en.wikibooks.org/wiki/Algorithms/Hill_Climbing

          當前有很多種演算法可以解決運算子優先順序的問題,比如排程場演算法遞迴下降演算法移進歸約演算法等。

          那為什麼YACEP 會選用優先爬山演算法呢?

          因為爬山演算法是我看過眾多的演算法中,理解起來是最快的。下面講解一下這個演算法的大致思路。

          在開始之前我需要先回憶一下小學學到的數學知識,四則運演算法則和結合律。

          四則運演算法則告訴我們當一個式子有加減乘除的時候,先算乘除後算加減,這個是一個原則,很好理解,照著做就成。

          結合律就稍複雜一點了,結合律是說在一個包含有二個以上的可結合運運算元的表示式,只要運算元的位置沒有改變,其運算的順序就不會對運算出來的值有影響。看不懂是吧,換一種說法就是:

( a + b ) + c = a + ( b + c )   

       a, b的和 再與c求和的值 一定 等於 a與  b, c的和  求和的值。請務必按照這個斷句讀,否則有點拗口。

       如果讀完還不知道啥意思,可以回小學去捶一頓你們的體育老師了。

       好了,有了上面的知識儲備,我們開始研究如下數學表示式:

a + b * c * d + e

            按照四則運演算法則: 先乘除後加減,對於上述表示式,我們標記一下運算的優先順序

         再按照結合律,對於更高優先順序的乘法,在求 a*b*c 的值的時候,我們無所謂先算 a*b 再用這個結果去乘c,還是先算b*c再去乘a。按照當前主流的文字閱讀規則,我們選擇從左往右,即先算b*c,然後拿到這個結果再乘d。我們將b*c的結果儲存為m,於是上述表示式可以簡化為:

// m = b * c
a + m * d + e

       再繼續,我們將m*d的結果儲存為n,於是上述表示式再次簡化為:

// m = b * c
// n = m * d
a + n + e

         再次按照結合律,我們將a+n的結果儲存為x, 於是上述表示式還可簡化為:

// m = b * c
// n = m * d
// x = a + n
x + e

        到這一步只要求x+e的結果就好了,把前面的步驟合併在一起。

a + b * c * d + e
    -----
      m    
    ---------
       n
---------------
         x
----------------------

      去掉字元,我們把下面的虛線連線起來,大概是這樣:

       山頂
      / ----- \
      /          \
      / ---------   \
      /                \
      / -------------     \
      /                     \
      / -----------------     \

      這就是傳說中的爬山演算法!!!

     是不是超好懂!!!

     10秒鐘讀懂了爬山演算法!!!     

      其實怎麼說呢,理解到這一步基本就離理解完整的爬山演算法差的沒幾百步了。總共也就幾百步,你再走幾百步就完全理解了,加油,我們繼續。

      按照上述的步驟,我們現在開始理解演算法的流程。我們將表示式中的元素分為兩類,一類我們叫原子值,比如上面表示式中的a、b、c、d、e,另一類我們叫他們為運算子,比如+和*。對於帶括號的表示式,我們可以稱其為子表示式,也是一種表示式,所以我們總是可以將任何一個表示式拆解為只包含原子值或運算子的表示式(對於一個不包含運算子的表示式,直接拿值就完了)。

      基於上述的分類,我們開始描述一下爬山演算法的運算步驟:

               1. 讀取當前一個原子值和它最鄰近的運算子

               2. 讀取下一個原子值和它最鄰近的運算子

               3. 如果步驟1中的運算子的優先順序大於步驟2中的優先順序,則演算法返回當前原子值,當前原子值最鄰近運算子與下一個原子值的子表示式

               4. 否則,從步驟2開始構建新的子表示式繼續按照上述步驟處理

      回到表示式:

a + b * c * d + e

      現在按照爬山演算法開始處理這個表示式:

  1.  讀到原子值a以及優先順序為1的運算子+, a + 
  2.  讀取下一個原子值b以及優先順序為2的運算子*, b * 
  3.  優先順序2大於1,所以我們開始從b開始構建子表示式 
  4.  讀取下一個原子值c以及優先順序為2的運算子*, c * 
  5.  當前的子表示式的  b * 優先順序是2(,新讀到   c * 的優先順序也是2,得到新表示式 b * c * , 優先順序是2。上面的步驟的值  m * 
  6.  讀取下一個表示式  d + , +的優先順序是1,所以整個子表示式返回值為  m * d ,這兩步需要理解結合律。上面的值  n +  
  7.  子表示式已經處理完成,退出子表示式的處理流程,當前的表示式為的優先順序為步驟1中, a +
  8.  下一個表示式為 n +  ,兩者的優先順序都是1,返回子串  a + n 上面的值 x + 
  9.  下一個表示式為  e  ,直接返回
  10.  得到最終表示式  x + e 

       回到之前我們得到的那座山,再看看這個步驟,你會發現這個流程還真的就是在爬這座山 。

       山頂
      / ----- \
      /          \
      / ---------   \
      /                \
      / -------------     \
      /                     \
      / -----------------     \

2.2 利用ReadOnlySpan加速字串解析

         C#中String物件是一個只讀物件,一旦建立將不可更改。所以C#中對String物件做更改的方法底層都會建立一個新的String物件。比如在如下程式碼中:

 1             unsafe
 2             {
 3                 var random = new Random();
 4                 var str = "";
 5                 for (int i = 0; i <= 5; i++)
 6                 {
 7                     str += Convert.ToChar(random.Next('A', 'Z'));
 8                     fixed (char* p = str)
 9                         Console.WriteLine((int)p);
10                 }
11                 Console.WriteLine(str);
12             }

        上述的程式碼是在做字串修改,你可能會覺得這種修改返回一個新值沒問題。

      但是下面的這種情況對於解析器來說就是一種致命傷了。在擷取字串時,你會發現每一次值都是不一樣的,縱使你擷取的位置是相同的,Substring始終如一的返回一個新物件給你。

 1             unsafe
 2             {
 3                 var str = "123456";
 4                 fixed (char* p = str)
 5                     Console.WriteLine((int)p);
 6                 fixed (char* p = str.Substring(1, 2))
 7                     Console.WriteLine((int)p);
 8                 fixed (char* p = str.Substring(1, 2))
 9                     Console.WriteLine((int)p);
10                 Console.WriteLine(str);
11             }

          對解析過程而言,可能會有頻繁截串的場景,比如隨時都可能要將表示式中的一段數字轉換為一個數值。這種情況,每次都返回一個新的字串物件,無論效能還是記憶體都是難以接受的。

       你可能有想到C#中的StringBuilder物件,它確實是維護一個緩衝區,可以在做字串修改的時候保證始終如一的使用同一塊地址,但是這玩意是用來構建字串的,讀取字串這貨不行的,所以你看官方連個Substring都不給你。

      難道必須使用非託管程式碼了麼?為了保證更快的記憶體讀取以及更低的記憶體消耗,難道我要去PInvoke???

           

      這種問題,微軟的碼農肯定已經意識到了,不然這部分的隨筆。。。我怎麼寫下去

      微軟提供了System.Memory程式集用來幫助我們更方便也更安全的操作記憶體。

      我們可以使用ReadOnlySpan來解決上述問題。

      ReadOnlySpan在程式集System.Memory中,是Span的只讀表示。將字串轉換為一個ReadOnlySpan物件,接著使用ReadOnlySpan來處理字串,那麼上述的問題都可以被解決。

      然後大致說一下SpanSpan可以用於表達任何一段連續記憶體空間,無論是陣列,非託管指標,可獲取到指標值的託管記憶體等等等等(是不是回憶起當初被指標支配的恐懼感),其實在它內部的實現就是一個指標。相對於C/C++裡面的指標需要各種小心翼翼,不敢有一絲怠慢忘記釋放,或訪問到離奇的地址,或因為各種原因變成野指標。Span會在內部維護這個指標的地址,在做指標運算時,會做邊界檢查,在更新引用時,垃圾回收器也能判斷出該如何回收記憶體。

      對於Span的解讀,推薦閱讀下面這個系列,作者的解讀非常贊。現在願意寫博文講清 What、How 和 Why的博主不多了,且讀且珍惜。

      https://www.cnblogs.com/justmine/p/10006621.html

2.3 利用表示式樹和Emit生成表示式執行代理

     動態生成代理是一個古老的話題。最開始是因為大家都覺得.NET自帶的那個反射操作太慢,怎麼說呢,其實對於大部分場景是夠用的,某知名大佬說:

每每看人在談論程式碼時,都說那反射操作是極慢的,萬萬不可取。

在我自己,卻認為反射之慢不過毫秒。

用不正當的思路寫出的程式碼才會引起真正的慢。

                                                       -- 樹人Groot

         

      之所以會成為一個古老的話題,是因為動態生成代理會出現在太多的業務場景中。

      最常見的就是快速獲取一個物件指定名稱的成員值,你會看到各色愛寫庫愛造輪子的大佬非常熱衷去搞的個快速物件訪問器什麼的。

      多年前部落格園大佬趙姐夫還參與過此事寫過一個庫,地址如下:

            http://blog.zhaojie.me/2009/02/fast-reflection-library.html

      :雖然老趙已經很久沒有更新部落格了,但是他的部落格還是非常推薦去閱讀一下,內容很豐富,乾貨特別多。

          部落格地址:http://blog.zhaojie.me

      好了,回過頭來。現在對使用動態生成代理的場景做一個彙總,常出現的場景如下:

  1.  物件序列化反序列化,這種場景多出現於RPC中,代理要把stream轉換為object
  2.  實現ORM,代理要把reader轉換為object(這種其實也是rpc)
  3.  實現AOP,代理要為具體類生成一個包含一系列切面函式的類
  4.  繫結求值,比如模板渲染或YACEP這種對編譯結果呼叫執行獲取結果的過程

        動態生成代理實現方式有如下四種:

  1.  利用Expression構造代理方法
  2.  DynamicMethod生成動態函式構造代理方法
  3.  DynamicAssembly構建代理類
  4.  利用CodeDom動態生成程式集,生成代理

       下面大致對上面的四種方式做一個比較

  Expression DynamicMethod DynamicAssembly CodeDom
優點

實現的程式碼簡單易讀

原生支援繞過CLR訪問修飾符檢查 支援生成型別 支援生成型別
缺點 不支援生成型別 需要對IL指令有一定了解 需要對IL指令有一定了解 程式碼臃腫
適用場景 邏輯稍簡單的代理 邏輯稍簡單的代理 功能更完備的代理 處理模板化的程式碼

           YACEP在定義可執行物件時, 沒有使用.NET內建的委託,而是定義了兩個介面。

    public interface IEvaluator
    {
        object Evaluate(object state);
    }
    public interface IEvaluator<in TState>
    {
        object Evaluate(TState state);
    }

 

          那為什麼使用介面而不是用委託來定義可執行物件,委託才更符合對可執行物件表述的直覺啊 ?

          這是因為YACEP需要支援自定義字面量,自定義函式。

          如果生成的是委託,最佳的做法是生成閉包函式,在閉包中儲存這些自定義字面量和函式,用EMIT生成一個返回閉包函式的函式。

          這種做法確實可以做到,問題是寫出來的EMIT程式碼會更多,更難除錯和定位錯誤,如何知道是函式的閉包問題還是函式自身問題呢。

          那如果不用EMIT的方式生成委託,還可以使用表示式樹。是的,表示式樹在這個方面處理起來比EMIT更有優勢,程式碼可讀性更好。而且使用表示式樹還有個巨大的優勢,YACEP編譯的本質其實是將YACEP自定義的抽象語法樹轉換為C#抽象語法樹,表示式樹所定義的抽象語法樹幾乎和YACEP自定義的抽象語法樹是一比一的,這種轉換要比生成IL更簡單。

          那為什麼YACEP不使用表示式樹呢?

          在實現YACEP的編譯過程時,我會先腦補出抽象語法樹轉換出的IL程式碼,然後是最終的C#程式碼。如何檢驗YACEP生成的結果與我腦補的結果是一致呢?

          如果使用表示式樹,我需要debug看錶達式樹的樹結構。如果時間允許,我倒是十分願意去做這樣的事情,可惜YACEP只是我換工作間隙拿來練手的小玩意。EMIT支援生成動態程式集,然後再用ILSpy去檢查生成IL程式碼是否符合我預期,讀生成的DLL程式碼可是比去看錶達式樹的樹結構來的簡單。

         所以最終在YACEP的程式碼裡,你可以看到即有表示式樹的程式碼也有EMIT的程式碼。按照上表的適用場景,表示式樹用於在處理按照給你名稱獲取或設定物件成員值,EMIT為表示式生成最終的執行代理。


3. 工具篇

3.1 測試覆蓋率工具 - Coverlet 

            測試覆蓋率是啥就不解釋了。YACEP使用xUnit.net做單元測試,在當前以及未來可能的版本中,YACEP始終要求100%的行覆蓋率,99%以上的分支覆蓋率。

            如何做測試覆蓋率統計?

            目前 Visual Studio是支援檢視測試覆蓋率的,如果安裝了JetBrains的dotCover外掛,可以獲得更好的體驗(要錢的)。

            這些是用來看測試覆蓋率的,如何搞到測試覆蓋率報表呢?

            dotnet在跑測試時,可以通過配置資料收集器來生成測試覆蓋率報告(更詳細配置文件),示例命令如下:

 dotnet test --collect:"Code Coverage"

           在專案中執行此命令,就會生成一個*.coverage的檔案在TestResults資料夾裡面。

           噠噠噠噠!我來開啟這個檔案,看看我的覆蓋率是不是已經100%了呢!

           二進位制的,還是專屬檔案格式???

           沒事,興許在命令的執行結果裡面能找到覆蓋率的值!

Microsoft (R) Test Execution Command Line Tool Version 16.0.1                                                                                 
Copyright (c) Microsoft Corporation.  All rights reserved.                                                                                    
                                                                                                                                              
Starting test execution, please wait...                            
                                                            
Total tests: 80. Passed: 80. Failed: 0. Skipped: 0.                                                                                           
Test Run Successful.                                                                                                                          
Test execution time: 4.4614 Seconds                                                                                                           

          嗯!

          報告很簡潔嘛!

          我想要的覆蓋率呢,到底是個啥數啊!!!

          二進位制檔案非得軟體開,我特麼VSCode使用者啊!!!

         命令列執行過程就說跑了多少測試 ,我特麼寫了多少測試我自己不清楚麼!!!

       那有沒有啥工具可以生成不需要特定軟體才能開啟的覆蓋率報告,還能在我跑test命令的時候直白的告訴我到底覆蓋了多少程式碼呢?

      要是還能支援一下VSCode就更好了!!!

      沒有的,別想了,乖乖回去寫程式碼,不要有非分之想!

      非分之想是個好東西!

      你都不非分一下,咋知道是不是真的沒有!

      Coverlet - https://github.com/tonerdo/coverlet

      官方介紹是這麼寫的!

Coverlet is a cross platform code coverage library for .NET Core, with support for line, branch and method coverage.

      好像可以嘗試一下的樣子啊!

      安裝.net core global tool

dotnet tool install --global coverlet.console

      新增依賴到測試專案中

dotnet add package coverlet.msbuild

      帶著漠視整個世界的感情開始執行測試

dotnet test ./tests/TupacAmaru.Yacep.Test/TupacAmaru.Yacep.Test.csproj ^
    /p:CollectCoverage=true ^
    /p:Exclude=\"[xunit.*]*,[TupacAmaru.Yacep.Test*]*\"^
    /p:CoverletOutputFormat=\"lcov,opencover\" ^
    /p:CoverletOutput=./../../results/coverage/

      看輸出,好像比之前的輸出多出了一些不得了的東西啊

Calculating coverage result...
  Generating report '.\..\..\results\coverage\coverage.info'
  Generating report '.\..\..\results\coverage\coverage.opencover.xml'

+------------------+------+--------+--------+
| Module           | Line | Branch | Method |
+------------------+------+--------+--------+
| TupacAmaru.Yacep | 100% | 98.9%  | 100%   |
+------------------+------+--------+--------+

+---------+------+--------+--------+
|         | Line | Branch | Method |
+---------+------+--------+--------+
| Total   | 100% | 98.9%  | 100%   |
+---------+------+--------+--------+
| Average | 100% | 98.9%  | 100%   |
+---------+------+--------+--------+

      行覆蓋率,分支覆蓋率,方法覆蓋率一目瞭然,使用還簡單,無需做任何程式碼修改,只需要引用一下,再跑一下.net core global tool就好了!

     

     那你以為這個工具的能力就到此為止了嗎?

     沒有的!

     VSCode有個擴充套件:Coverage Gutters,https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters

     它支援顯示全部的語言的程式碼覆蓋情況,只要你能給它一個 lcov格式的檔案。

     Coverlet 是支援生成lcov格式的檔案的,這樣你就可以在跑完測試去看你的程式碼到底哪些地方沒有被覆蓋了。

     簡直是我等屌絲程式設計師的福音有沒有,感謝耶穌大佬,感謝釋迦摩尼先生。

3.2 覆蓋率報表轉換工具 - ReportGenerator

     上面的Coverlet配合Coverage Gutters基本可以解決開發過程中98%的程式碼覆蓋問題。

     那還有2%呢?

     是很多時候我們需要能夠更直觀看覆蓋率的情況,我們需要對的人類更易讀的報表。

     我們不太可能任何時候都開啟VSCode找到Icov檔案,再找到原始碼逐個去看具體程式碼到底有沒有被覆蓋。

     我們需要一種能夠脫離開發環境,脫離原始碼去檢視覆蓋率的報表。

     最佳的檔案格式是什麼呢?

     當然是HTML!

     傳送HTML的報告給其他人,告訴他,二貨!快,開啟你的瀏覽器,看,我的覆蓋率,已經100%!

     是100%哦!      

     

    但是一想到還不知道有沒有工具能夠支撐我們裝逼的內心,就會對著這個世界,悵然若失,淡淡的開始感嘆,人間不值得。

    所幸,人間是值得的!!!

    ReportGenerator - https://github.com/danielpalme/ReportGenerator

   官方介紹是這麼寫的!

 

ReportGenerator converts coverage reports generated by OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo or Clover into human readable reports in various formats.

 

The reports do not only show the coverage quota, but also include the source code and visualize which lines have been covered.

 

ReportGenerator supports merging several reports into one.

 

 

    恩!給力!

    再往下看!!!

    竟然提供了.net core global tool。

    安裝.net core global tool,搞起!!!

dotnet tool install -g dotnet-reportgenerator-globaltool

    生成HTML格式的報表!

reportgenerator  "-reports:results/coverage/coverage.opencover.xml" "-targetdir:./results/coverage/reports"

 瀏覽器開啟生成的index.html檔案

     

     現在做工具的都好良心!

     操作簡單,功能強大,最關鍵的還不要錢。

     感謝耶穌大佬,感謝釋迦摩尼先生。

3.3 基準測試工具 - BenchmarkDotNet

      但凡喜歡造輪子(我已經潛意識的把我排除在外了:))的都喜歡一個詞 - 高效能,為了能夠證明自己的輪子效能很贊,出現了各色各樣的效能測試工具。

     個人推薦 BenchmarkDotNet - https://benchmarkdotnet.org/

     這次就不貼官方介紹了,太長了,有興趣的可以自己去看,英文的!

     這裡我把官方的說法做一個簡單的翻譯,大致是這麼個意思。

     老郭:搞基準測試不簡單啊!

     老於:咋?您老是搞出啥問題了?

     老郭:您看啊,這要考慮咋設計執行迭代次數?

     老於:對,是這理兒!

     老郭:還要考慮咋做執行預熱?

     老於:對,是這理兒!

     老郭:還要考慮咋樣支援不同平臺?

     老於:對,是這理兒!

     老郭:人間不值得啊!

     老於:啊? 剛剛人家和尚還說人間是值得的,還感謝耶穌大佬,感謝釋迦摩尼先生呢!

     老郭:那您?是有好招?

     老於:對的,推薦一個小玩意給您得了!

     老郭:甭管啥東西?得!放著好用就成,您給我掰哧掰哧!

     老於:知道金坷垃麼?

     老郭:金坷垃?

     老於:哦,不?是BenchmarkDotNet!

     老郭:那是嘛玩意?

     老於:您啊,剛說的那些問題BenchmarkDotNet都能解決!比如說這設計迭代執行次數的問題,人家這庫自動幫您選,都不帶要您動手的!

     老郭:這勁兒勁兒的!

     老於:庫那邊,引用一下,給您的類或方法打幾個特性標記一下,程式碼就搞完了!

     老郭:這勁兒勁兒的!

     老於:想測試不一樣的平臺?什麼.netfx,.netcore,corert和mono啊,統統支援!

     老郭:這勁兒勁兒的!

     老於:想測試不一樣的處理器架構?x86支援!x64支援!

     老郭:這勁兒勁兒的,勁兒勁兒的!

     老於:想測試不一樣的JIT版本?LegacyJIT支援!RyuJIT支援!

     老郭:這倍兒勁兒啊!

     老於:GC標記不一樣也想測?伺服器,工作站都支援!除此以外,還支援各種引數搞出不一樣的結果!

     老郭:我算是聽明白了,是個夠勁兒的玩意,那它支援生成啥樣的報告呢?

     老於:GitHub的文件格式,StackOverflow的文件格式,RPlot,CSV,JSON,XML還有HTML!哎,還有特性我還沒說完呢,您這是要幹啥去啊!

     老郭:我回家開電腦去啊!

     老於:這火急火燎的回去下種子?啥片子啊,給我也發一份啊!

     老郭:我回家開電腦開VS下BenchmarkDotNet去啊!

      在GitHub上,tupac-amaru 組織下有個開源的專案benchmarks: https://github.com/tupac-amaru/benchmarks

      這個專案是一個基於BenchmarkDotNet 的一個基準測試專案模板。

      包含了一個基準測試專案的腳手架工程。還有準備了可以直接執行的指令碼。

      面向windows使用者的上可用的bat指令碼和Docker指令碼。

      面向Mac/Linux上可用的sh指令碼和Docker指令碼。

      有興趣的可用去看一下。因為編寫基準測試的程式碼已經有具體的示例專案,網上也有大量的相關博文,這裡就不再做詳細介紹。

      不過網上對很少有解讀生成的報告的,在這裡我嘗試解讀一下。

      在YACEP的基準測試報告中:https://github.com/tupac-amaru/yacep/tree/_benchmark#atomicvaluestring 

      上面的報告來自程式碼:https://github.com/tupac-amaru/yacep/blob/master/tests/TupacAmaru.Yacep.Benchmark/AtomicValue/String.cs

      這個程式碼是用來測試YACEP將表示式中的字串轉換為C#字串的能力。

      字串和數值是表示式的幾大基本資料型別之一,所以YACEP必須要對字串負責,不能面對字串表示式硬氣不起來,要硬,要有一定的效能。

      上面的報告有兩部分資訊。

      第一部分顯示的是這個基準測試執行的宿主機配置以及為基準測試配置的引數。

      宿主機配置資訊:

BenchmarkDotNet=v0.11.5, OS=ubuntu 16.04
Intel Xeon CPU E5-2673 v3 2.40GHz, 1 CPU, 2 logical and 2 physical cores
.NET Core SDK=2.2.204
  [Host] : .NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT
  • 所用BenchmarkDotNet的版本是0.11.5
  • 作業系統是Ubuntu 16.04
  • CPU型號E5-2673 v3 2.40GHz, 1 CPU, 2 logical and 2 physical cores
  • 後面一部分是.NET Core的資訊

      為基準測試配置的引數(官方文件):

Toolchain=InProcessEmitToolchain  InvocationCount=8  IterationCount=200  
LaunchCount=1  RunStrategy=Throughput  UnrollFactor=4  
WarmupCount=1  
  • Toolchain引數,用來配置用於生成,構建和執行具體基準測試工具鏈的型別。InProcessEmitToolchain引數的意思是用在程式內用emit的方式生成用於做基準測試的物件,而不生成新的可執行檔案去獨立執行。
  • InvocationCount,是指在單次迭代中一個方法的呼叫次數。該值必須是後面值UnrollFactor的倍數。
  • IterationCount,是指在測試過程中迭代的總數。
  • LaunchCount,是指需要使用的程式總數。
  • RunStrategy,這個引數在這個基準測試中配置其實無意義的,不指定的話,預設值就是Throughput。
  • UnrollFactor,是指基準測試的過程中方法在迴圈中迴圈執行的次數。
  • WarmupCount,是指預熱的次數 

      第二部分是一張表,有六列,各列的意思大致如下:

  • Method,代表測試的方法。如果一個基準測試中包含多個測試方法,就可以用來橫向比較各個方法的測試結果
  • StringLength,這是一個自定義引數(程式碼)。表示YACEP處理的隨機字串長度。
  • Mean,是指方法執行時間的平均值,和後面的中值不一樣,{1, 2, 5, 8}的均值是(1+2+5+8)/4=4, {1, 2, 5, 8, 10}的均值是(1+2+5+8+10)/5=5.2
  • Error,置信區間
  • StdDev,執行時間的偏差,值越大,偏差越大
  • Median,是指方法執行時間的中值,和前面的均值不一樣,{1, 2, 5, 8}的中值是中間兩個數的均值(2+5)/2=3.5, {1, 2, 5, 8, 10}的中值是中間那個值5

      好了,現在來解讀一下這個報告的最後一行。

      從表示式中將長度為1000的字串轉換C#字串,YACEP平均處理時間是194.1ns. 

         注:1秒=1000000000 納秒,以後誰說C#效能不好,去,錘他!

 


 

 4. 服務篇

     藉助於上面的工具,我們已經可以在本地做單元測試,計算覆蓋率,拿到更易讀的覆蓋率報告以及基準測試報告。

     但是!這還不夠好,我們的程式碼不可能總是要人去跑測試,要人去執行命令列程式碼獲取覆蓋率,要人去跑基準測試再拿具體的報告。

     很早很早之前一位來自中國的和尚曾有言:

道求道,佛家求善,儒學求中庸。如是搞IT的,當求懶!

                                                       -- 沃·茲基碩德

     說的對,搞IT的就是應該求!!!

  

 

     懶???難道意味著啥都不做?

嘿,老闆!我,告訴你,作為搞IT的,大佬說要追求懶!

所以,從明天開始,我~決定啥都不做!

面對我~這樣一個有追求的程式設計師,請記得!要多發一倍工資給我!     

老闆看了看你,徑直走到門前,關上門,再慢悠悠的走到窗前,緩緩的拉下百葉窗。

偌大的辦公室暗了下來。

整個辦公室,除了你,就剩他。

只見點燃一根菸,靠著沙發坐了下來,若有所思。

沉默著抽完一整支菸。

正欲張口,忽聽前臺小妹一聲尖叫。

心中暗道:莫不是婦產科醫院的結果出來了?

欲知後事如何,請看下篇!

相關文章