如何编写高性能的C#代码(二)

使用Benchmark.NET对C# 代码进行基准测试的简介

在我以前的文章中[10],我介绍了该系列文章[11],在其中我将分享我的经验,同时了解C#和.NET Core(corefx)框架的新性能。在本文中,我想着重于对现有代码进行基准测试并建立基准。

为什么要对C#代码进行基准测试?

我开始进行基准测试的原因是,在我们能够并且应该开始优化代码之前,我们应该首先了解我们的当前位置。这对于确保我们的变更正在产生我们所希望的影响,并且最重要的是不使我们的性能变差至关重要。以我的经验,性能工作是一个反复的过程或度量,需要进行小的更改并再次进行度量以检查更改的效果。

可以说,在本系列文章中我可能已经开始了其他一些工作,可能是通过概要分析,跟踪或度量收集。所有这些可能都是必需的,以便针对应优化的服务,并在代码级,类和方法上成为您的目标。

我决定暂时跳过这些更高级别的技术,部分原因是我对这些领域并不完全有信心,当然我可以为他们提供良好的指导。而且,它们是有关性能的一系列主题,我认为这会分散我对语言和框架功能的关注。

对于实际情况,您可能需要使用此类技术来首先缩小应该花费时间进行优化的位置。对于真实的场景,您可能需要首先使用这些技术来缩小您应该花时间优化的位置范围。有时可以做出正确的猜测,但只要有可能,最好是在你的努力中具有科学性,并以实际数据支持理论。我可能有一天会回到这些更广泛的领域,但是现在,我假设您对要改进的代码路径有所了解。

如果您确实想了解有关对代码进行概要分析的更多信息,那么我阅读了Konrad Kokosa的 “Pro .NET Memory Management: For Better Code, Performance, and Scalability[12]”这本书,并从中学到了很多东西。

基准测试是在代码的典型条件下确定当前性能的过程。在.NET中,在代码级别,有许多可行的技术。有时,使用简单的秒表将是收集常规计时数据的起点。

请注意,许多情况可能会影响您的测量及其准确性。秒表的优点是使用简单,可以快速提供结果。我认为以这种方式收集一些基本数据没有什么错,只要可以理解准确性的折衷即可。

一旦将注意力集中在代码的特定领域,您就会开始深入到方法级别。

在这一点上,开始为现有方法和代码记录更准确和特定的基准非常有用。这是基准测试应成为您选择的工具的地方。在C#中,我们有一个Benchmark.NET[13]形式的绝佳选择。该库提供了大量基准测试工具,可用于测量和基准测试.NET代码。现在,Microsoft团队经常使用Benchmark .NET来衡量其代码。

什么是基准?

基准仅仅是与某些代码的执行有关的一种测量或一组测量。通过基准测试,您可以在开始努力提高性能时比较代码的相对性能。

基准测试的范围可能很广,或者您经常会发现自己正在测试微观基准的微小变化。最主要的是确保您具有一种机制,可以将建议的更改与原始代码进行比较,从而指导优化工作。在优化代码时,使用数据而不是假设很重要。

如何对C#代码进行基准测试

希望到现在为止,您已经对基准的概念有所了解,所以让我们从一个简单的例子开始。如果您想继续阅读,可以在此示例存储库[14]的“基准”分支上找到此文章的完整代码。

public class NameParser{public string GetLastName(string fullName){var names = fullName.Split(" ");var lastName = names.LastOrDefault();return lastName ?? string.Empty;}  }

假设我们已经确定以下NameParser是我们的应用程序在重负载和潜在性能瓶颈下的一个区域。 此代码是一个简单的实现,用于从输入字符串中返回姓氏,该字符串被假定为人的全名。就本演示而言,它假定最后一个单词,在任何空格代表姓氏之后。

目前,这只是一个简化的示例,您可能要进行基准测试的方法可能会完成更复杂的工作!有时,您可以从现有代码库中直接引用和基准测试代码,这些方法足够小且公开。在其他时候,我发现自己通过将代码的相关部分复制到我的基准测试项目中来创建基准测试,以便将重点放在特定的代码行上。

我需要在这方面花更多的时间来确定围绕构建基准的良好做法。

1、安装Benchmark.NET库

第一步是安装Benchmark.NET库。通常,因为您可能已经在进行单元测试,所以您将创建一个单独的项目来保存基准。在此基准测试项目中,您将引用包含要基准测试的代码的项目。为了使我的示例保持简单,我现在将所有内容留在一个项目中。

对于一般基准,您只需要NuGet的主要BenchmarkDotNet软件包。我通过在命令行中使用

“ dotnet  add package BenchmarkDotNet –version 0.11.3”

将其添加到示例项目中来安装我的系统。

2、建立基准

下一步是通过创建一个包含基准的新类来创建基准基准测试类将由Benchmark.NET运行,并且任何基准测试方法的结果将包含在输出中。这是我的NameParserBenchmarks类。

[MemoryDiagnoser]public class NameParserBenchmarks{private const string FullName = "Steve J Gordon";private static readonly NameParser Parser = new NameParser();[Benchmark(Baseline = true)]public void GetLastName(){Parser.GetLastName(FullName);}       }

类本身用BenchmarkDotNet.Attributes命名空间中的属性标记。Benchmark.NET具有诊断程序的概念,可以控制要测量并包含在结果中的事物。在没有附加任何附加诊断程序的情况下,它将仅提供基准数据的时序数据。内存诊断程序支持分配和GC收集的其他度量,这在优化代码时非常有帮助。

在前面的代码中,我有一个名为GetLastName的方法,该方法通过调用它对NameParser类中现有的GetLastName方法进行基准测试。我已经用Benchmark属性标记了此方法,以便Benchmark.NET执行该方法并将其包含在结果中。我可以在此处为基线属性提供一个值,以将该特定方法标记为基线。这是我们正在测量的现有代码,以后将很有用,因为所有其他基准都将与该初始代码进行比较。

为了支持基准测试,我在基准测试中包含了要解析的名称的静态字符串值。我还包括一个静态字段,其中包含对新NameParser实例的引用。我不想将它们包括在Benchmark方法本身中,因为我想单独测量GetLastName方法的性能和分配。

3、运行Benchmark.NET

最后一步是为Benchmark.NET设置并触发运行程序。在此示例中,我将运行单个项目中的所有内容,因此将更新Program类的Main方法。

通用的BenchmarkRunner.Run方法的调用接受应为其运行任何基准的类。默认情况下,基准测试结果将记录到控制台。

public class Program{public static void Main(string[] args){var summary = BenchmarkRunner.Run<NameParserBenchmarks>();}}

执行基准

在此阶段,我们准备运行基准测试。为了获得最佳结果,建议您在运行最少的设备上执行此操作。关闭所有其他应用程序并杀死不必要的进程将产生最稳定的结果。在开发机器上,一旦一切都关闭,我将触发从命令行运行基准测试。

应针对发布代码运行基准测试,以确保包括所有优化。在我的项目目录中,我将运行

“ dotnet build -c Release”

以创建一个发布版本。

构建完成后,我可以导航到包含构建代码的文件夹:“ cd bin / Release / netcoreapp2.2”

最后,我可以通过对示例应用程序使用“ dotnet BenchmarkAndSpanExample.dll”运行构建的程序集来运行基准测试

运行基准测试所需的时间长短取决于您的计算机和受测试的代码。Benchmark.NET执行许多阶段来预热代码,并确保运行多次迭代以提供一致的统计数据。它使用一个试验阶段来计算要运行的最佳迭代次数,尽管您可以根据需要进行配置。

解释结果

完成后,您应该将摘要结果写入控制台窗口。如果愿意,可以在运行应用程序的位置下的BenchmarkDotNet.Artifacts文件夹中生成各种输出。其中包括摘要的HTML版本,可以更轻松地共享。

我的机器的摘要如下所示:

图片

对于每种基准测试方法,您将在一行中包含结果数据。在这里,我只有一行用于我的GetLastName方法的基准测试。平均执行时间为125.8纳秒;不是太寒酸!其他统计数据可用于迭代中时序数据的误差和标准偏差。

因为我包括了memory diagnosticr属性,所以我包括了一些额外的列,其中包含与内存相关的统计信息。前三列与GC集合有关。它们按比例缩放以显示每1,000个操作的数量。在这种情况下,必须经常调用我的方法才能触发Gen 0集合,并且不太可能导致Gen 1或Gen 2集合。最后一栏非常有帮助,它显示了每个操作分配的内存。我的名称解析器代码当前每次被分配160个字节。在宏伟的计划中,这根本不算什么,但是我们将在以后的文章中看到如何减少这种情况。请记住,尽管.NET中的分配很便宜,但GC收集和清理这些对象的工作可能会带来更多影响。在热路径(被称为方法)中,这很快就会加起来。

在我的第一篇文章中[15],我提到了一个工人流程,该流程每天维护17至2000万个事件。如果在处理每个事件时需要调用此GetLastName方法,则每天将导致3.2GB的分配!如此规模如此之小,很快就可以加起来!

摘要

在尝试对代码进行任何优化工作之前,始终先建立基线是非常重要且重要的。这样,您可以真正看到改进后的代码是否比原始代码更快和/或分配的更少。

评估改进可以帮助指导进一步的优化,并且还可以提供关键数据,这些数据可以证明花费时间进行此类改进的代码。使用Benchmark.NET之类的工具进行基准测试非常简单,只需进行简单的工作,几乎不需要花什么功夫,就可以轻松比较代码性能。

在本文中,我们已经了解了如何使用Benchmark .NET为现有代码提供基线,以了解其运行速度和分配的内存量。在下一篇文章中,我将介绍Span,我们将使用Benchmark .NET来衡量改进。

谢谢阅读!如果您想了解有关高性能.NET和C#代码的更多信息,可以在此处[16]查看我的完整博客文章系列。

References

[1] Writing High-Performance .NET Code: https://www.amazon.co.uk/gp/product/0990583457/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0990583457&linkCode=as2&tag=stevejgordon-21&linkId=9345b81c7b89459a2015a61e7470abb9
[2] 编写高性能.NET代码: https://item.jd.com/15217405980.html
[3] Pro .NET Memory Management: For Better Code, Performance, and Scalability: https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X/ref=sr_1_1?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&keywords=.NET+Memory+Management&qid=1583662848&sr=8-1
[4] Blogs: https://adamsitnik.com/
[5] talks: https://www.youtube.com/watch?v=CSPSvBeqJ9c
[6] 博客: https://adamsitnik.com/
[7] 演讲: https://www.youtube.com/watch?v=CSPSvBeqJ9c
[8] 博客: https://blog.marcgravell.com/
[9] 在此处: https://www.stevejgordon.co.uk/writing-high-performance-csharp-and-dotnet-code
[10] 以前的文章中: https://www.stevejgordon.co.uk/motivations-for-writing-high-performance-csharp-code
[11] 文章: https://www.stevejgordon.co.uk/motivations-for-writing-high-performance-csharp-code
[12] Pro .NET Memory Management: For Better Code, Performance, and Scalability: https://www.amazon.co.uk/gp/product/148424026X/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=148424026X&linkCode=as2&tag=stevejgordon-21&linkId=fc3f451494b7fdcefdfa03674f1cd2da
[13] Benchmark.NET: https://benchmarkdotnet.org/
[14] 此示例存储库: https://github.com/stevejgordon/BenchmarkAndSpanExample/tree/Benchmarks
[15] 第一篇文章中: https://www.stevejgordon.co.uk/motivations-for-writing-high-performance-csharp-code
[16] 在此处: https://www.stevejgordon.co.uk/writing-high-performance-csharp-and-dotnet-code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311248.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何编写高性能的C#代码(一)

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。作者介绍&#xff1a;史蒂夫戈登&#xff08;Steve Gordon&#xff09;是Microsoft MVP&#xff0c;Pluralsight的作者&#xff0c;…

从Java转向.NET/C#,Are You OK?

最近由于项目变动&#xff0c;需要用.NET/C#做开发&#xff0c;经过一段时间的学习和培训&#xff0c;对这个技术栈有了一定的理解。大家可能都知道Java和.NET/C#很像&#xff0c;这里粗略的把两者做一个对比&#xff0c;希望对感兴趣的童鞋有所帮助。如果现在有人问我&#xf…

树的节点值之和

题目背景 墨家家主有棵树。 题目描述 给定一个保存树节点信息的数据结构&#xff0c;它包含了树节点唯一的 id &#xff0c;树节点值 和 直系子节点的 id 。 比如&#xff0c;树节点1是树节点2的父节点&#xff0c;树节点2是树节点3的父节点。他们相应的树节点值为 9 , 4 , …

.NET Core开发实战(第21课:中间件:掌控请求处理过程的关键)--学习笔记(上)...

21 | 中间件&#xff1a;掌控请求处理过程的关键这一节讲解一下如何通过中间件来管理请求处理过程中间件工作原理next 表示后面有一个委托&#xff0c;每一层每一层套下去可以在任意的中间件来决定在后面的中间件之前执行什么&#xff0c;或者说在所有中间件执行完之后执行什么…

简单的二叉树创建与遍历

编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以指针方式存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;空格字符代表空树。建立起此二叉树以后&…

疫情期间,千万级系统宕机N次,老板撂下狠话:没法把性提升10倍,全员解雇!...

性能调优整体思路作为一名团队技术核心&#xff0c;如何让系统跑得通、跑得稳、跑得快是必然会面对的场景。性能分析是一个大课题&#xff0c;不同的架构、不同的应用场景、不同的程序语言分析的方法若有差异&#xff0c;抽象一下大致分为两类&#xff1a;自底向上&#xff1a;…

Anaconda创建python虚拟环境

在创建虚拟环境之前首先我们需要打开命令终端&#xff1a;Win R 输入cmd 或者直接打开Anaconda Prompt&#xff08;Anaconda&#xff09; pycharm下载历史版本地址&#xff1a;https://www.jetbrains.com/pycharm/download/other.html Anaconda下载历史版本地址&#xff1a;ht…

[蓝桥杯][算法提高VIP]夺宝奇兵-递推+记忆化搜索

题目描述 在一座山上,有很多很多珠宝,它们散落在山底通往山顶的每条道路上,不同道路上的珠宝的数目也各不相同.下图为一张藏宝地图: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 ”夺宝奇兵”从山下出发,到达山顶,如何选路才能得到最多的珠宝呢?在上图所示例子中,按照5-> 7-> 8-&g…

梯度下降与线性回归

对于代价函数&#xff1a; loss∑i(y^−yi)2loss\sum_i{(\hat{y}-y_i)}^2loss∑i​(y^​−yi​)2 loss∑i(w∗xib−yi)2loss\sum_i{(w*x_ib-y_i)}^2loss∑i​(w∗xi​b−yi​)2 最常见的代价函数&#xff1a;均方差代价函数&#xff08;Mean-Square Error&#xff0c;MSE&…

.NET Core开发实战(第21课:中间件:掌控请求处理过程的关键)--学习笔记(下)...

21 | 中间件&#xff1a;掌控请求处理过程的关键如果在 Map 的时候逻辑复杂一点&#xff0c;不仅仅判断它的 URL 地址&#xff0c;而且要做特殊的判断的话&#xff0c;可以这么做把判断逻辑变成一个委托我们要判断当我们的请求地址包含 abc 的时候&#xff0c;输出 new abcapp.…

英伟达3060Ti安装GPU版本TensorFlow2.X和Pytorch

查看Python与TensorFlow对应版本 安装GPU版本的TensorFlow的时候&#xff0c;我们需要考虑的一个问题是Python版本与TensorFlow版本的对应关系&#xff0c;可以参考下面这个链接&#xff1a; Python对应TensorFlow CPU版本 GPU版本 查看显卡驱动对应的CUDA版本并且下载安装 …

200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]

2019年1月19日&#xff0c;微软技术&#xff08;苏州&#xff09;俱乐部成立&#xff0c;我受邀在成立大会上作了一个名为《ASP.NET Core框架揭秘》的分享。在此次分享中&#xff0c;我按照ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架&…

逻辑回归(二)

逻辑回归 在学习逻辑回归之前我们先回顾一下线性回归。线性回归解决的是回归问题&#xff0c;简单来说就是&#xff0c;我们需要找到一个函数&#xff0c;这个函数需要尽可能的拟合所有训练集的样本点。 逻辑回归解决的是分类问题&#xff0c;它的目标是找到一个函数&#x…

上元节的灯会(灭)-区间dp

题目背景 上元节的庙会上&#xff0c;牛宝靠自己的聪明才智成功破解了花灯阵&#xff0c;点亮了在场所有花灯&#xff0c;但他没料到的是这个游戏包含AB两个项目&#xff0c;A项目就是点亮所有花灯&#xff0c;而B项目则是熄灭所有花灯。不过点亮的是花灯阵&#xff0c;熄灭的…

Asp.Net Core 中IdentityServer4 授权中心之应用实战

一、前言查阅了大多数相关资料&#xff0c;搜索到的IdentityServer4 的应用文章大多是比较简单并且多是翻译官网的文档编写的&#xff0c;我这里在 Asp.Net Core 中IdentityServer4 的应用分析中会以一个电商系统架构升级过程中普遍会遇到的场景进行实战性讲述分析&#xff0c;…

交通标志识别项目教程

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 安装Anaconda 安装Pycharm 安装格式工厂 在上图中这个位置输入cmd回车&#xff0c;即可打开命令终端。用这样的方式打开命令终端…

C# 视频监控系统

去过工厂或者仓库的都知道&#xff0c;在工厂或仓库里面&#xff0c;会有很多不同的流水线&#xff0c;大部分的工厂或仓库&#xff0c;都会在不同流水线的不同工位旁边安装一台电脑&#xff0c;一方面便于工位上的师傅把产品的重要信息录入系统&#xff0c;便于公司系统数据采…

sklearn svm如何选择核函数_机器学习之支持向量机多种核模型对比

机器学习xueyifeiyun1989zx&#xff0c;公众号&#xff1a;围着围巾的小黑机器学习之监督学习实战前文我们提到机器学习中的监督学习&#xff0c;其中有一个模型是我们提到的但是没有训练测试的&#xff0c;叫做支持向量机(简称SVM)。支持向量机也是监督学习里面一个非常容易理…

程序员过关斩将--从每秒6000写请求谈起

点击上方“蓝字”关注我们菜菜哥&#xff0c;紧急求助呀怎么回事&#xff1f;产品经理砍你了&#xff1f;没有&#xff0c;只是写了个新项目&#xff0c;上线就被压垮了什么功能&#xff0c;这么强悍&#xff1f;一个记录用户观看视频进度信息的功能那如果用户基数大&#xff0…

批量将PPM格式图片转化为JPG格式

将PPM格式图片转化为JPG格式 做图像识别的时候数据集常常是ppm格式的&#xff0c;虽然不影响建模训练&#xff0c;但是我们电脑往往不支持ppm格式的图像展示。 比如到做交通标志识别的时候用到的BelgiumTS交通数据集或者德国GTSRB数据集 下载后得到都是ppm格式的图像。 格式转…