以前一篇博客中,咱們講解.NET Core中的CSV解析庫,在文章的最後,做者使用了性能基準測試工具BenchmarkDotNet測試了2個不一樣CSV解析庫的性能,本篇咱們來詳細介紹一下BenchmarkDotNet。html
原文連接:https://dotnetcoretutorials.com/2017/12/04/benchmarking-net-core-code-benchmarkdotnet/程序員
性能基準測試能夠幫助程序員對比2個代碼段或者方法的性能,這對於代碼重寫或者重構來講,能夠提供一種很好的量化標準。若是沒有性能基準測試,很難想象將方法A改成B方法時候,僅憑肉眼如何區分性能的變化。app
BenchmarkDotNet是一款強力的.NET性能基準測試庫, 官網https://benchmarkdotnet.org/。函數
運行時支持工具
BenchmarkDotnet爲每一個被測試的方法提供了孤立的環境, 使用BenchmarkDotnet, 程序員能夠很容易的編寫各類性能測試方法,並能夠避免許多常見的坑。性能
如今咱們但願來對比一下Linq to object中First和Single方法的性能測試
雖然咱們知道First的性能確定比Single高, First方法會在查詢到第一個知足條件的對象以後就中止集合遍歷,而Single找到第一個知足條件的對象以後,不會中止查找,它會去繼續查找集合中的剩餘對象,直到遍歷整個集合或者在集合中找到第二個匹配條件的對象。 這裏咱們只是爲了演示一下如何進行代碼基準測試。spa
爲了使用BenchmarkDotNet來進行代碼基準測試,咱們首先建立一個空的.Net Core控制檯程序。調試
而後咱們使用Package Manage Console添加BenchmarkDotNet庫code
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(); } }
代碼解釋說明
SingleVsFirst
類是一個測試類。Single
和First
方法,分別調用了Linq to object的SingleOrDefault
和FirstOrDefault
方法來查詢字符串集合中的"needle"字符串。Single
和First
方法上,咱們加入[Benchmark]
特性, 擁有該特性的方法會出如今最後的基準檢測報告中。注意:
- 測試的方法必須是公開的(public), 若是把public去掉,程序不會產生任何結果
- 在運行程序以前,還有一步關鍵的操做,測試的程序須要使用Release模式編譯,而且不能附加任何調試器(Debugger)
如今咱們運行程序,程序產生的最終報告以下
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 |
結果中的第一列Mean代表了2個方法處理的平均響應時間,First
比Single
快了一倍(這和咱們測試字符串放置的位置有關係)。
BenchmarkDotNet中咱們還可使用[ParamsSource]
參數來指定測試的用例範圍。
在上面的代碼中,咱們測試了匹配字符串在集合中間位置時,First
和Single
的效率對比,下面咱們修改上面的代碼,咱們但願分別測試匹配字符串在集合頭部,尾部以及中間位置時First
和Single
的效率對比。
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(); } } }
代碼解釋說明
_needles
Needle
, 表示當前測試的用例,在被測試Single
和First
方法中,咱們使用屬性Needle
來匹配[ParamsSource]
, 並設置參數來源是_needles
如今咱們運行程序,程序產生的最終報告以下
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
從結果上看
First
性能比Single
高的多First
性能是比Single
的一倍First
和比Single
的性能差很少在.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]特性,咱們還在測試類CsvBenchmarking
上添加了[MemoryDiagnoser]
特性,該特性會在測試報告中追加,2個方法執行時的內存使用狀況。
Method | Mean | Scaled | Allocated | -------------- |-----------:|-------:|----------:| CSVHelper | 1,404.5 ms | 1.00 | 244.39 MB | TinyCsvParser | 381.6 ms | 0.27 | 32.53 MB |
其中Allocated代表了內存佔用狀況。
BenchmarkDotNet絕對是.NET開發人員瞭解代碼性能,以及對比代碼性能的必備神器。你的項目裏用了BenchmarkDotnet了麼?