.NET Core中的性能測試工具BenchmarkDotnet

背景介紹

之前一篇博客中留夜,我們講解.NET Core中的CSV解析庫真椿,在文章的最后浑劳,作者使用了性能基準(zhǔn)測試工具BenchmarkDotNet測試了2個不同CSV解析庫的性能秋泄,本篇我們來詳細介紹一下BenchmarkDotNet琐馆。

原文鏈接:https://dotnetcoretutorials.com/2017/12/04/benchmarking-net-core-code-benchmarkdotnet/

為什么需要性能基準(zhǔn)測試?

性能基準(zhǔn)測試可以幫助程序員對比2個代碼段或者方法的性能,這對于代碼重寫或者重構(gòu)來說恒序,可以提供一種很好的量化標(biāo)準(zhǔn)。如果沒有性能基準(zhǔn)測試谁撼,很難想象將方法A改為B方法時候歧胁,僅憑肉眼如何區(qū)分性能的變化。

BenchmarkDotNet

image

BenchmarkDotNet是一款強力的.NET性能基準(zhǔn)測試庫, 官網(wǎng)https://benchmarkdotnet.org/厉碟。

運行時支持

  • NET Framework (4.6+),
  • .NET Core (2.0+)
  • Mono
  • CoreRT喊巍。

BenchmarkDotnet為每個被測試的方法提供了孤立的環(huán)境, 使用BenchmarkDotnet, 程序員可以很容易的編寫各種性能測試方法,并可以避免許多常見的坑箍鼓。

代碼基準(zhǔn)測試(Code Benchmarking)

現(xiàn)在我們希望來對比一下Linq to object中First和Single方法的性能

雖然我們知道First的性能肯定比Single高, First方法會在查詢到第一個滿足條件的對象之后就停止集合遍歷崭参,而Single找到第一個滿足條件的對象之后,不會停止查找款咖,它會去繼續(xù)查找集合中的剩余對象何暮,直到遍歷整個集合或者在集合中找到第二個匹配條件的對象。 這里我們只是為了演示一下如何進行代碼基準(zhǔn)測試铐殃。

為了使用BenchmarkDotNet來進行代碼基準(zhǔn)測試海洼,我們首先創(chuàng)建一個空的.Net Core控制臺程序。

image

然后我們使用Package Manage Console添加BenchmarkDotNet庫

PM> Install-Package BenchmarkDotNet

然后我們修改Program.cs文件, 代碼如下

    public class Program
    {
        public class SingleVsFirst
        {
            private readonly List<string> _haystack = new List<string>();
            private readonly int _haystackSize = 1000000;
            private readonly string _needle = "needle";

            public SingleVsFirst()
            {
                //Add a large amount of items to our list. 
                Enumerable.Range(1, _haystackSize).ToList().ForEach(x => _haystack.Add(x.ToString()));
                //Insert the needle right in the middle. 
                _haystack.Insert(_haystackSize / 2, _needle);
            }

            [Benchmark]
            public string Single() => _haystack.SingleOrDefault(x => x == _needle);

            [Benchmark]
            public string First() => _haystack.FirstOrDefault(x => x == _needle);

        }

        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<SingleVsFirst>();
            Console.ReadLine();
        }
    }

代碼解釋說明

  • 以上代碼中<code>SingleVsFirst</code>類是一個測試類富腊。
  • 測試類中我們生成了一個擁有100萬對象的字符串集合坏逢。
  • 我們在集合的中間位置插入了一個測試字符串,字符串的內(nèi)容是"needle"赘被。
  • 代碼中的<code>Single</code>和<code>First</code>方法是整,分別調(diào)用了Linq to object的<code>SingleOrDefault</code>和<code>FirstOrDefault</code>方法來查詢字符串集合中的"needle"字符串。
  • 在<code>Single</code>和<code>First</code>方法上民假,我們加入<code>[Benchmark]</code>特性, 擁有該特性的方法會出現(xiàn)在最后的基準(zhǔn)檢測報告中浮入。

注意:

  • 測試的方法必須是公開的(public), 如果把public去掉,程序不會產(chǎn)生任何結(jié)果
  • 在運行程序之前阳欲,還有一步關(guān)鍵的操作舵盈,測試的程序需要使用Release模式編譯陋率,并且不能附加任何調(diào)試器(Debugger)

最終結(jié)果

現(xiàn)在我們運行程序,程序產(chǎn)生的最終報告如下

Method  |     Mean |     Error |   StdDev |   Median |
------- |---------:|----------:|---------:|---------:|
 Single | 28.12 ms | 0.9347 ms | 2.697 ms | 28.93 ms |
  First | 13.30 ms | 0.8394 ms | 2.475 ms | 14.48 ms |

結(jié)果中的第一列Mean表明了2個方法處理的平均響應(yīng)時間秽晚,<code>First</code>比<code>Single</code>快了一倍(這和我們測試字符串放置的位置有關(guān)系)瓦糟。

帶測試參數(shù)的基準(zhǔn)測試(Input Benchmarking)

BenchmarkDotNet中我們還可以使用<code>[ParamsSource]</code>參數(shù)來指定測試的用例范圍。
在上面的代碼中赴蝇,我們測試了匹配字符串在集合中間位置時菩浙,<code>First</code>和<code>Single</code>的效率對比,下面我們修改上面的代碼句伶,我們希望分別測試匹配字符串在集合頭部劲蜻,尾部以及中間位置時<code>First</code>和<code>Single</code>的效率對比。


using System;
using System.Collections.Generic;
using System.Linq;
 
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
 
namespace BenchmarkExample
{
    public class SingleVsFirst
    {
        private readonly List<string> _haystack = new List<string>();
        private readonly int _haystackSize = 1000000;
 
        public List<string> _needles => new List<string> { "StartNeedle", "MiddleNeedle", "EndNeedle" };
 
        public SingleVsFirst()
        {
            //Add a large amount of items to our list. 
            Enumerable.Range(1, _haystackSize).ToList().ForEach(x => _haystack.Add(x.ToString()));
 
            //One at the start. 
            _haystack.Insert(0, _needles[0]);
            //One right in the middle. 
            _haystack.Insert(_haystackSize / 2, _needles[1]);
            //One at the end. 
            _haystack.Insert(_haystack.Count - 1, _needles[2]);
        }
 
        [ParamsSource(nameof(_needles))]
        public string Needle { get; set; }
 
        [Benchmark]
        public string Single() => _haystack.SingleOrDefault(x => x == Needle);
 
        [Benchmark]
        public string First() => _haystack.FirstOrDefault(x => x == Needle);
 
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<SingleVsFirst>();
            Console.ReadLine();
        }
    }
}

代碼解釋說明

  • 我們創(chuàng)建了測試的用例字符串集合<code>_needles</code>
  • 在構(gòu)造函數(shù)中考余,我們在字符串集合的頭部先嬉,中部,尾部分別插入了3個字符串
  • 我們添加了一個屬性<code>Needle</code>, 表示當(dāng)前測試的用例楚堤,在被測試<code>Single</code>和<code>First</code>方法中疫蔓,我們使用屬性<code>Needle</code>來匹配
  • 在屬性Needle上我們加上了參數(shù)來源特性<code>[ParamsSource]</code>, 并設(shè)置參數(shù)來源是<code>_needles</code>

最終效果

現(xiàn)在我們運行程序,程序產(chǎn)生的最終報告如下

 Method |       Needle |             Mean |          Error |           StdDev |           Median |
------- |------------- |-----------------:|---------------:|-----------------:|-----------------:|
 Single |    EndNeedle | 23,266,757.53 ns | 432,206.593 ns |   591,609.263 ns | 23,236,343.07 ns |
  First |    EndNeedle | 24,984,621.12 ns | 494,223.345 ns |   783,890.599 ns | 24,936,945.21 ns |
 Single | MiddleNeedle | 21,379,814.14 ns | 806,253.579 ns | 2,377,256.870 ns | 22,436,101.14 ns |
  First | MiddleNeedle | 11,984,519.09 ns | 315,184.021 ns |   924,380.173 ns | 12,233,700.94 ns |
 Single |  StartNeedle | 23,650,243.23 ns | 599,968.173 ns |   714,219.431 ns | 23,555,402.19 ns |
  First |  StartNeedle |         89.17 ns |       1.864 ns |         2.732 ns |         89.07 ns

從結(jié)果上看

  • 當(dāng)匹配字符串在集合頭部的時候身冬,<code>First</code>性能比<code>Single</code>高的多
  • 當(dāng)匹配字符串在集合中部的時候衅胀,<code>First</code>性能是比<code>Single</code>的一倍
  • 當(dāng)匹配字符串在集合尾部的時候,<code>First</code>和比<code>Single</code>的性能差不多

加入內(nèi)存測試

.NET Core中的CSV解析庫中酥筝,我們使用了以下代碼

    [MemoryDiagnoser]
    public class CsvBenchmarking
    {
        [Benchmark(Baseline =true)]
        public IEnumerable<Automobile> CSVHelper()
        {
            TextReader reader = new StreamReader("import.txt");
            var csvReader = new CsvReader(reader);
            var records = csvReader.GetRecords<Automobile>();
            return records.ToList();
        }
     
        [Benchmark]
        public IEnumerable<Automobile> TinyCsvParser()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, ',');
            var csvParser = new CsvParser<Automobile>(csvParserOptions, new CsvAutomobileMapping());
     
            var records = csvParser.ReadFromFile("import.txt", Encoding.UTF8);
     
            return records.Select(x => x.Result).ToList();
        }
    }

其中除了[Benchmark]特性滚躯,我們還在測試類<code>CsvBenchmarking</code>上添加了<code>[MemoryDiagnoser]</code>特性,該特性會在測試報告中追加嘿歌,2個方法執(zhí)行時的內(nèi)存使用情況掸掏。

        Method |       Mean | Scaled | Allocated |
-------------- |-----------:|-------:|----------:|
     CSVHelper | 1,404.5 ms |   1.00 | 244.39 MB |
 TinyCsvParser |   381.6 ms |   0.27 |  32.53 MB |

其中Allocated表明了內(nèi)存占用情況。

總結(jié)

BenchmarkDotNet絕對是.NET開發(fā)人員了解代碼性能搅幅,以及對比代碼性能的必備神器阅束。你的項目里用了BenchmarkDotnet了么?

本文源代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茄唐,一起剝皮案震驚了整個濱河市息裸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沪编,老刑警劉巖呼盆,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚁廓,居然都是意外死亡访圃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門相嵌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腿时,“玉大人况脆,你說我怎么就攤上這事∨悖” “怎么了格了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長徽鼎。 經(jīng)常有香客問我盛末,道長,這世上最難降的妖魔是什么否淤? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任悄但,我火速辦了婚禮,結(jié)果婚禮上石抡,老公的妹妹穿的比我還像新娘檐嚣。我一直安慰自己,他們只是感情好汁雷,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布净嘀。 她就那樣靜靜地躺著,像睡著了一般侠讯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暑刃,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天厢漩,我揣著相機與錄音,去河邊找鬼岩臣。 笑死溜嗜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的架谎。 我是一名探鬼主播炸宵,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谷扣!你這毒婦竟也來了土全?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤会涎,失蹤者是張志新(化名)和其女友劉穎裹匙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體末秃,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡概页,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了练慕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惰匙。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡技掏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出项鬼,到底是詐尸還是另有隱情哑梳,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布秃臣,位于F島的核電站涧衙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奥此。R本人自食惡果不足惜弧哎,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稚虎。 院中可真熱鬧撤嫩,春花似錦、人聲如沸蠢终。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寻拂。三九已至程奠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祭钉,已是汗流浹背瞄沙。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慌核,地道東北人距境。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像垮卓,于是被迫代替她去往敵國和親垫桂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容