编写高性能的C#代码(三)使用SPAN

原文来自互联网,由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除。

作者介绍:

史蒂夫·戈登(Steve Gordon)是Microsoft MVP,Pluralsight的作者,布莱顿(英国西南部城市)的高级开发人员和社区负责人。

编写高性能的C#代码(三)使用SPAN 

这篇文章继续了我有关编写高性能C#代码的系列文章[1]。在本文中,我们将通过介绍Span 类型从上两篇文章继续,并通过将其转换为基于Span的版本来重构一些现有代码。我们将使用Benchmark.NET比较这些方法并验证我们的更改是否改进了代码。

如果您想遵循示例代码,可以在GitHub上找到[2]

什么是SPAN ?

Span是C#7.2引入的一种新类型,在.NET Core 2.1运行时中受支持。现有的.NET Standard 1.0运行时都有一个.NET Standard实现,但是在.NET Core中,我将重点介绍运行时更改,以支持可能的最佳版本,也称为“fast span”。

Span提供对内存连续区域的类型安全访问。该内存可以位于堆,堆栈上,甚至可以由非托管内存组成。Span具有相关的类型ReadOnlySpan ,该类型提供内存中数据的只读视图。ReadOnlySpan可用于查看不可变类型(例如字符串)占用的内存。我更喜欢将Span视为进入某些现有内存的窗口,而不管其分配在何处。

图片

在上图中,Span 引用一些已经分配的连续内存。现在,我们在该内存上有了一个窗口。

Span 被定义为引用结构,这意味着它仅限于仅在堆栈上分配。这减少了一些潜在的用例,例如将其存储为类中的字段或在异步方法中使用它。这些限制可以通过使用类似的新型Memory来解决,我们将在以后的文章中介绍它。引用结构设计的主要原因是要确保在使用Span时,我们不会引起其他堆分配。这是它支持高性能代码路径中如此高度优化的用例的原因之一。

我将避免为这篇文章过多地介绍实现细节(毕竟这是一篇介绍),而将重点放在一个示例中,我们可能在哪里使用它以及它如何影响我们的基准。

如果您想阅读有关Span的更多详细信息,我建议以下链接:

•Span 结构[3]•有关Span的所有信息:探索新的.NET主体[4]•C#7.2:了解Span[5]•Span By Adam Sitnik[6]

加快现有代码的速度并减少分配

在上一篇文章中,我们对一些代码进行了基准测试,这些代码用于从全名字符串中“解析”姓氏。通过Benchmark.NET,我们确定该方法需要125.8 ns的时间运行,并且每次运行分配160个字节。

在使用基于Span 的方法进行重构之前,我希望这是一个公平的竞争,因此我将首先不使用Span 来优化代码。这有望成为一个很好的例子,因为它着重指出,即使不使用Span之类的新功能,也可以通过对正在执行的工作进行一些思考来优化现有代码。

当前代码在任何空格上分割字符串,这将组成一个字符串数组。如果考虑到这一点,我们将分配一个数组,在使用名称“ Steve J Gordon”的情况下,这样做时将分配三个较小的字符串“ Steve”,“ J”和“ Gordon”。正如我们在基准测试中所看到的那样,这会导致分配160个字节。

对于查找姓氏的要求,我们不在乎存储名称的所有部分,而只是存储我们希望是姓氏的最后一部分。请注意,在此示例中,我忽略了多词姓氏等情况!

让我们向NameParser添加另一个方法,该方法而不是拆分字符串,而是获取最后一个空格字符的索引,并使用该方法获取代表姓氏的子字符串。

public string GetLastNameUsingSubstring(string fullName){var lastSpaceIndex = fullName.LastIndexOf(" ", StringComparison.Ordinal);return lastSpaceIndex == -1? string.Empty: fullName.Substring(lastSpaceIndex + 1);}

首先,我们获取全名字符串中最后一次出现空格的索引。如果它为-1,则找不到任何空格,因此我们将返回一个空字符串作为默认结果。如果找到索引,则使用Substring方法提取姓氏并将其返回。

我们稍后会将该版本包含在我们的基准测试中。但是,实际上,值得在进行代码改进的每个迭代时对其进行测试,以验证您是在改进方面还是使它们变得更糟。

使用SPAN 

让我们看看这次如何使用Span 重新编写此代码。在高性能需求旺盛的场景中,我们既要提高速度又要减少代码中的内存分配。

 public ReadOnlySpan<char> GetLastNameWithSpan(ReadOnlySpan<char> fullName){var lastSpaceIndex = fullName.LastIndexOf(' ');return lastSpaceIndex == -1 ? ReadOnlySpan<char>.Empty : fullName.Slice(lastSpaceIndex + 1);}

首先要注意的是,方法参数“ fullName”现在的类型为ReadOnlySpan 。某些类型(例如字符串)可以隐式转换为chars的ReadOnlySpan,因此此方法签名可以正常工作。现在,返回类型也是ReadOnlySpan。

首先,以与上面的优化代码非常相似的方式,我们寻找空格字符的最后一个索引。

同样,如果其值为-1,则我们找不到空格,并且将返回空的ReadOnlySpan 结果。

如果找到空格字符,我们现在可以使用Span的一种功能,即“切片”(Slice)。

切片是一项非常强大的操作,我们可以将现有的Span和“切片”放到更紧密的窗口中。切片时,我们为切片指定起始位置的索引,并为切片指定终止位置的长度。省略长度会导致从起始位置到Span结束的切片。

切片是一种低成本的操作,因为我们不复制任何内容,而只是创建一个新的Span,该Span表示一个进入现有内存范围子集的窗口。

图片

在上图中,我们可以创建原始Span的Slice来查看其中的5个元素,而无需分配原始内存的任何其他副本。

在新的基于Span的代码中,我们从空格字符后的索引处开始获取fullName的一部分。由于我们未指定长度,因此此切片将运行到现有Span的末尾。

对Span进行切片后,会在切片的部分上产生一个新的Span,然后将其作为方法的结果返回。

至此,我们有两个潜在的改进代码版本,一个使用Substring,另一个使用Span 。让我们更新基准并比较结果。

衡量改进基准

添加两个新基准后,基准类现在如下所示:

[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][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);}[Benchmark]public void GetLastNameUsingSubstring(){Parser.GetLastNameUsingSubstring(FullName);}[Benchmark]public void GetLastNameWithSpan(){Parser.GetLastNameWithSpan(FullName);}}

我们定义了三个基准,每个基准在NameParser中采用不同的方法。运行基准测试在我的计算机上给出以下结果…

图片

此列表中的最后一项是我们原始的GetLastName方法。因为我们要求获得排名结果,并且此方法运行的最慢,所以它在最后显示出来。

这次大约花了125ns的时间运行,当然仍然分配了160个字节。

第二快的是我们尝试在不使用Span 的情况下改进代码的情况,该代码使用Substring。此代码比原始方法快大约3倍。重要的是,我们现在将分配减少到只有40个字节。这说明了我们在调用子字符串时要分配的姓氏字符串。

总的赢家是基于Span 的方法。这比我们的原始代码快10倍,比基于子字符串的方法快2.8倍。

这里真正重要的是,因为我们要对Span进行切片以查找姓氏的位置,并且还返回Span作为方法的输出,所以我们永远不会分配新的字符串。通过已分配的内存状态(现在为空)可以明显看出这一点。

图片

对于单个调用,节省的160个字节(或子字符串方法节省40个字节)并不庞大,但是在特定场景下上,节省的费用加起来了。

如果此代码需要在我维护的每天处理约2000万条消息的数据处理服务中运行,那么我们每天将节省3.2 GB的分配。这些可能是短暂的分配,但是即使如此,它们仍将导致垃圾回收。根据估算的Gen 0 / 1k操作数(译者注,是指0代回收,每次回收1k字节),原始代码每天将触发2,000个操作,共506个GC。

这是CPU时间和暂停时间,我们可以通过避免分配任何资源来帮助减少时间。

摘要

在本文中,我们研究了新的Span 类型,并使用它重构了一些代码以实现最佳性能。最初,Span听起来可能有点复杂,但正如我希望我已经展示的那样,在本示例中使用它非常简单。

谢谢阅读!

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

References

[1] 有关编写高性能C#代码的系列文章: https://www.stevejgordon.co.uk/motivations-for-writing-high-performance-csharp-code
[2] 示例代码,可以在GitHub上找到: https://github.com/stevejgordon/BenchmarkAndSpanExample
[3] Span 结构: https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-2.2
[4] 有关Span的所有信息:探索新的.NET主体: https://msdn.microsoft.com/en-us/magazine/mt814808.aspx
[5] C#7.2:了解Span: https://channel9.msdn.com/Events/Connect/2017/T125
[6] Span By Adam Sitnik: https://adamsitnik.com/Span/
[7] 在此处: https://www.stevejgordon.co.uk/writing-high-performance-csharp-and-dotnet-code

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

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

相关文章

pycharm配置git拉取项目代码,并添加版本控制

安装Git 打开网页进入git官网&#xff0c;找到git官网下载地址&#xff0c;下载git工具并且安装。 pycharm配置git 点击File -> Settings -> Version Control -> Git 选择Git安装的路径&#xff0c;点击OK 选择一个项目 进入我们需要拉取的项目&#xff0c;点击…

mpu 配置内存空间_mpu内存保护单元功能及工作原理

一些嵌入式系统使用多任务的操作和控制。这些系统必须提供一种机制来保证正在运行的任务不破坏其他任务的操作。即要防止系统资源和其他一些任务不受非法访问。嵌入式系统有专门的硬件来检测和限制系统资源的访问。它能保证资源的所有权&#xff0c;任务需要遵守一组由操作环境…

poj3981 字符串替换-字符串的基本操作

Description 编写一个C程序实现将字符串中的所有"you"替换成"we" Input 输入包含多行数据 每行数据是一个字符串&#xff0c;长度不超过1000 数据以EOF结束 Output 对于输入的每一行&#xff0c;输出替换后的字符串 Sample Input you are what you do…

.NET Core开发实战(第22课:异常处理中间件:区分真异常与逻辑异常)--学习笔记(上)...

22 | 异常处理中间件&#xff1a;区分真异常与逻辑异常这一节我们来讲解一下错误处理的最佳实践系统里面异常处理&#xff0c;ASP.NET Core 提供了四种方式1、异常处理页2、异常处理匿名委托方法3、IExceptionFilter4、ExceptionFilterAttribute源码链接&#xff1a;https://gi…

MYSQL开窗函数详解

基本概念 MYSQL8.0支持窗口函数&#xff08;Window Function&#xff09;&#xff0c;也称分析函数。窗口函数与组分聚合函数类似&#xff0c;但是每一行数据都会生成一个结果。如果我们将mysql与pandas中的DataFrame做类比学习的话他们的对应关系如下&#xff1a; SQL分组聚…

callmode php_Rabbitmq各方法的作用详解

exchange_declare(direct_logs,direct,false,false,false);// 这个是申明交换器&#xff0c;如果没有申明就给默认队列的这个交换器&#xff0c;而且发送的类型默认是direct)顺序参数名默认值作用1$exchange无交换机名2$type无交换机类型&#xff0c;分别有direct、fanout、top…

不同数据库select语句显示前N行数据比对

不同数据SQL对比 有时候我们只想查看一下表的数据的前几行数据&#xff0c;如果不加限制条件的话&#xff0c;默认会查询整个表的数据&#xff0c;等待时间比较久。下面是不同数据库select语句显示前N行数据比对&#xff1a; SQL Server select top 10 * from table_name;D…

hdu2648 Shopping-map容器

Problem Description Every girl likes shopping,so does dandelion.Now she finds the shop is increasing the price every day because the Spring Festival is coming .She is fond of a shop which is called “memory”. Now she wants to know the rank of this shop’s…

你可能需要了解一下的中台

【中台学习】| 作者 / Edison Zhou这是恰童鞋骚年的第201篇原创文章在数字化转型热潮下&#xff0c;各家企业都想建设中台&#xff0c;那么中台是怎么发展起来的&#xff1f;有哪些类型的中台&#xff1f;中台到底是个啥&#xff1f;本文为你一一解答这些问题。1学习背景与前言…

cma检测_CMA检测方法

1、铰页位置偏移检测方法:以铰页轴为基准,用钢直尺测量C1、用有游标卡尺测量C2合格要求:前后,e≤0.2mm;上下或左右,e≤1mm2、门扇中心至门框下门槛尺寸检测方法:在门扇与门框装配好后,准确划好门扇中心线,用钢尺测量C3合格标准:H≤2000,一等,1.0mm二等,1.5mm三等,2.0mmH&#x…

github运行不流畅问题

快速流畅访问Github工具 下载链接如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1RwdrNK__Vx-AEuUr8sM6pg 提取码&#xff1a;a0tw –来自百度网盘超级会员V3的分享 下载后解压&#xff0c;双击运行.exe文件 运行后长这样&#xff0c;给它丢一边不管就行了。

strcmp()字符串比较函数

字符串比较函数strcmp的比较,两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。 基本形式为strcmp(str1,str2), 若str1str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数;

实现option上下移动_js: 实现Select的option上下移动 | 学步园

function UpOrDown(direct, selectId) {//direct : 1:Up, -1:Downvar obj document.getElementById(selectId);var len obj.length;var index obj.selectedIndex;//如果&#xff1a;1.没有选中的项; 2.向上&#xff0c;但已是最上; 3.向下&#xff0c;但是最下&#xff0c;不…

聊聊微信的Dark模式

大家好&#xff0c;我是Z哥。这周微信公布了一个我期待已久的好消息。周一的时候对外公布说&#xff0c;已经完成了iOS版本的Dark模式开发&#xff0c;可能会在下一个版本上线。▲截图来源于微博&#xff0c;版权归原作者所有真的是千呼万唤使出来&#xff0c;很多人期待这个功…

chrome浏览器快速访问stackoverflow

原因&#xff1a;国内网非常多的网站都使用免费的 Google CDN 服务来加载某些 js、字体样式库以提升网页浏览体验&#xff0c;例如 jQuery、Google Fonts。但是目前 Google 的大多数网站在大陆无法正常访问&#xff0c;因此这些本身是加快网页载入的库反而成为了阻塞网站加载的…

字符串哈希-BKDRHash

代码如下&#xff1a; #include <iostream> #include <vector> using namespace std; const int N 10005;struct node {char name[35]; };unsigned int BKDRHash(char *str) {unsigned int key 0, seed 31;while (*str) {key key * seed (*str);}return key …

linux程序已经在后台运行冻结了_Linux 让程序在后台执行

有些程序我们在打开时&#xff0c;会一直占用我们的终端&#xff0c;而且终端还不能关掉&#xff0c;所以这时候我们就需要让程序在后台运行。1.命令&#xff1a;nohupnohup python -u run.py > run.log 2>&1 &参数说明&#xff1a; run.py: 你需要后台运行的程序…

【开源要闻】Canonical发布新OpenStack工具、Kubernetes访客引导方法

Canonical发布支持CephFS的OpenStack Charms 20.02Canonical近日宣布了OpenStack Charms 20.02的全面上市&#xff0c;这是用于在Ubuntu上设计&#xff0c;构建和管理OpenStack私有云的强大工具的主要版本。OpenStack Charms 20.02是一个令人兴奋的版本&#xff0c;它增加了主要…

开窗函数(1)-部门工资前三员工

已知表 题目 公司的主管们感兴趣的是公司每个部门中谁赚的钱最多。一个部门的 高收入者 是指一个员工的工资在该部门的 不同 工资中 排名前三 。 编写一个SQL查询&#xff0c;找出每个部门中 收入高的员工 。 以 任意顺序 返回结果表。 示例 参考答案 selectbase.Departmen…

poj2182 Lost Cows-暴力

Description N (2 < N < 8,000) cows have unique brands in the range 1…N. In a spectacular display of poor judgment, they visited the neighborhood ‘watering hole’ and drank a few too many beers before dinner. When it was time to line up for their ev…