英文网站建设 济南,亚马逊电商平台入口,做视频网站是什么职业,腾讯云网站建设教学视频原文来自互联网#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权#xff0c;请联系小编#xff0c;小编将在24小时内删除。作者介绍#xff1a;史蒂夫戈登#xff08;Steve Gordon#xff09;是Microsoft MVP#xff0c;Pluralsight的作者#xff0c;… 原文来自互联网由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权请联系小编小编将在24小时内删除。作者介绍史蒂夫·戈登Steve Gordon是Microsoft MVPPluralsight的作者布莱顿英国西南部城市的高级开发人员和社区负责人。编写高性能的C#代码三使用SPAN 这篇文章继续了我有关编写高性能C代码的系列文章[1]。在本文中我们将通过介绍Span 类型从上两篇文章继续并通过将其转换为基于Span的版本来重构一些现有代码。我们将使用Benchmark.NET比较这些方法并验证我们的更改是否改进了代码。如果您想遵循示例代码可以在GitHub上找到[2]。什么是SPAN Span是C7.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]•C7.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 ReadOnlySpanchar GetLastNameWithSpan(ReadOnlySpanchar fullName){var lastSpaceIndex  fullName.LastIndexOf( );return lastSpaceIndex  -1 ? ReadOnlySpanchar.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?viewnetcore-2.2[4] 有关Span的所有信息探索新的.NET主体: https://msdn.microsoft.com/en-us/magazine/mt814808.aspx[5] C7.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