实际体验SpanT 的惊人表现

前言

最近做了一个过滤代码块功能的接口。就是获取一些博客文章做文本处理,然后这些博客文章的代码块太多了,很多重复的代码关键词如果被拿过来处理,那么会对文本的特征表示已经特征选择会有很大的影响。所以需要将这些代码块的部分给过滤掉。过滤起来很简单,就是找代码块的html 标记,然后将html标记之间的内容给删除就可以了。代码块的html标记一般都是<pre></pre>

我使用了String,Regex,StringBuilder,Span<T>这些不同的方法来实现这个功能,利用BenchMarks比较它们之间的性能差距。

BenchMarks

要对比不同代码之间的性能差距,还是不用StopWatch来计算消耗时间,这样简单的方法,而是使用BenchMarksDotNet包:一个专业的.net core下测试程序性能的工具包。

BenchMarksDotNet的github地址

这里简短介绍下BenchMarksDotNet的使用:

首先新建一个需要测试的类:FilterCodeBlocks ,并在类中写上被测试的方法:FilterCodeBlockByString

 public class FilterCodeBlocks{public string FilterCodeBlockByString(string content){return content;}}

然后新建一个类: FilterCodeBlocksBenchMark

using System;
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;namespace QuickSortBenchMarks
{[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][MemoryDiagnoser]public class FilterCodeBlocksBenchmarks{FilterCodeBlocks FilterCodeBlocks = new FilterCodeBlocks();[Benchmark]public void FilterByString(){FilterCodeBlocks.FilterCodeBlockByString(s);}}
}

最后在入口Progam.cs中 写上

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

执行dotnet build -c Release 然后 dotnet yourproject.dll 就可以看见BenchMarks测试效果.

铺垫好东西,现在开始进入正题。

使用 string

首先,直接用string 操作。由于测试博文可能会比较长,会有比较多的代码块。所以我的思路是,while(true) 去寻找代码块标记,并使用string 的寻址: indexOf() , 拼接:+= 和 剪切:Substring() 完成代码块的过滤。过程也很简单。这只是解决问题的一种方法,这篇文章的目的不是寻找最优解决方法,而是比较发现使用不同的 "工具" 之间的巨大性能差距。

        private static string _startTag = "<pre";private static string _endTag = "</pre>";private static int _startTagLength => _startTag.Length;private static int _endTagLength => _endTag.Length;public FilterCodeBlocks(){}public string FilterCodeBlockByString(string content){string result = "";while (true){var startPos = content.IndexOf(_startTag, StringComparison.CurrentCulture);if (startPos == -1)break;var content2 = content.Substring(startPos + _startTagLength, content.Length - startPos - _startTagLength);var endPos = content2.IndexOf(_endTag, StringComparison.CurrentCulture);result += content.Substring(0, startPos);content = content2.Substring(endPos + _endTagLength, content2.Length - endPos - _endTagLength);}result += content;return result;}

一开始选取了比较短的文本进行测试 ,可以直接写在程序中:

[RankColumn][Orderer(SummaryOrderPolicy.FastestToSlowest)][MemoryDiagnoser]public class FilterCodeBlocksBenchmarks{FilterCodeBlocks FilterCodeBlocks = new FilterCodeBlocks();public static string s = "<p>我们通过IndexWriterConfig 可以设置IndexWriter的属性," +"已达到我们希望构建索引的需求,这里举一些属性,这些属性可以影响到IndexWriter写入索引的速度:" +"</p>\n<div class=\"cnblogs_code\">\n<pre>IndexWriterConfig.setRAMBufferSizeMB" +"(<span style=\"color: #0000ff;\">double</span><span style=\"color: #000000;\">);" +"\nIndexWriterConfig.setMaxBufferedDocs(</span><span style=\"color: #0000ff;\">int</span><span " +"style=\"color: #000000;\">);\nIndexWriterConfig.setMergePolicy(MergePolicy)</span></pre>\n</div>\n<p>" +"setRAMBufferSizeMB()&nbsp;是设置";[Benchmark]public void FilterByString(){FilterCodeBlocks.FilterCodeBlockByString(s);}}

按照上述的方法,运行dll 得出 使用string 相关方法的性能。

平均处理时间 48微秒 分配内存 1.41kb,看来效果也是不错的,我感觉上面的代码中方法也是大家都会经常使用的方法。

接下来 .NET Core 2.1的新特性: Span 隆重登场!

Span< T >

What is a Span< T >?

Span< T > : 结构体值类型 。相当于C++ 中的指针,它是一段连续内存的引用,也就是一段连续内存的首地址。有了Span< T >,我们就可以不在unsafe的代码块中写指针了。Span< char > 相对于 string 也就具有很大的性能优势。

举个栗子: string.Substring() 函数,实际上是在堆中额外创建了一个新的 string 对象,把字符 copy 过去,再返回这个对象的引用。而相对应的 Span< T > 的Slice() 函数则是直接在内存中返回子串的首地址引用,此过过程几乎不分配内存,并且十分高效。

后面的优化也是使用Span< T > 的Slice() 代替了 string 的SubString() 。

简单看下 Span< T > 的源码,就可以窥见 Span< T > 的奥秘:

 public readonly ref partial struct Span<T>{/// <summary>A byref or a native ptr.</summary>internal readonly ByReference<T> _pointer;/// <summary>The number of elements this Span contains.</summary>private readonly int _length;....public Span(T[] array){if (array == null){this = default;return; // returns default}if (default(T) == null && array.GetType() != typeof(T[]))ThrowHelper.ThrowArrayTypeMismatchException();_pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()));_length = array.Length;}}

Span< T > 内部主要就是一个ByReference< T > 类型的对象,实际上就是ref T: 一个类型的引用,它和C 的int* char* 如出一折。Span < T > 也就是建立 ref 的基础上。

限定长度: _length ,就像 C 中定义指针,在使用前需要 malloc 或者 alloc 分配固定长度的内存。关于Span< T > 更多详细知识:

https://msdn.microsoft.com/en-us/magazine/mt814808.aspx

使用 Span< T > 优化

将上述 string 代码使用 Span< char > 优化一下

public string FilterCodeBlockBySpanAndToString(ReadOnlySpan<char> content){string result = "";ReadOnlySpan<char> contentSpan2 = new ReadOnlySpan<char>();int startPos = 0;int endPos = 0;ReadOnlySpan<char> startTagSpan = _startTag.AsSpan();ReadOnlySpan<char> endTagSpan = _endTag.AsSpan();while (true){startPos = content.IndexOf(startTagSpan);if (startPos == -1)break;contentSpan2 = content.Slice(startPos + _startTagLength, content.Length - startPos - _startTagLength);endPos = contentSpan2.IndexOf(endTagSpan);result += content.Slice(0, startPos).ToString();content = contentSpan2.Slice(endPos + _endTagLength, contentSpan2.Length - endPos - _endTagLength);}result += content.ToString();return result;}

这里 ReadOnlySpan<char> 是 Span< char > 的只读类型。使用Slice 代替SubString 。上述代码我依然返回的是 string。为了得到 string,我不惜使用Span< T > 的ToString() 函数,在我印象中,这个操作会把Span 的优势给拉回起跑线。

接下来看测试结果:

真是大吃一惊,平均消耗时间,居然少了 48000 纳秒,Span< T > 只是 string 的不到百分之一消耗。内存消耗减少了一半

Span< T >果然名不虚传,正如前面所说的SubString 和Slice 之间的性能差距。

Span< T > 的特色

虽然Span< T > 的性能十分出色 ,但是 string 有太多完善的接口,string 是为了简化你的代码让你更加舒服的使用字符串,所以牺牲了性能。因此 在对计算机消耗要求十分的严苛的情况下,尝试使用Span< T > ,大多数情况下,简短的string 已经能满足需求。我的认知下的Span< T >的特色:

  • Span< T >的定义方法多种多样,可以直接 ( i ) 像定义数组那样 : Span<int> a = new int[10]; ( ii ) 在构造函数中直接传入 数组(指针+长度)Span<T> a = new Span<T>(T[]),Span<T> a = new Span<T>(void*,length) ; ( iii )可以直接在栈中分配内存:Span<char> a = stackalloc char[10]; 在C# 8.0中才可以,这样的写法真是高大上。

  • Span< T > 只能存在于栈中,而不能放在堆中。因为 ( i ) GC 在堆中很难跟踪这些指针, ( ii ) 在堆中会出现多线程, 如果两个线程的两个Span< T >指向了同一个地址,那就糟了。

  • 可以使用 Memory< T > 代替 Span< T >在堆中使用。

  • 所有 string 的接口都可以用 Span< char > 来实现,这似乎又回到了原始的C语言时代。

  • Span < T > 有个兄弟叫 ReadOnlySpan< T > 。

到这里还不能结束Span< T >的性能评测。因为在大量字符串处理中还有个隐藏的实力派:正则表达式 Regex

正则表达式

如果我们使用正则表达式呢,它的性能会是如何呢?

正则表达式的实现:

  private static Regex _codeTag = new Regex("(<pre(.*?)>)(.|\n)*?(</pre>)", RegexOptions.Compiled);public string FilterCodeBlocByRegex(string content){return _codeTag.Replace(content, string.Empty);}

真是简短的让人看着就舒服。正则表达式的长处是在大文本处理,所以我决定直接将字符串变成100篇博客的内容加在一起。下面就是测试结果:

Incredible! 正则表达式 真的是一匹黑马,直逼Span< T >,时间消耗仅为10.68ms,内存消耗只有7.69MB。难得的是它的内存消耗也比Span< T >低。

为什么Regex会有这么好的表现呢?翻阅一下源码,原来如此!

private static string Replace(MatchEvaluator evaluator, Regex regex, string input, int count, int startat)
{....Span<char> charInitSpan = stackalloc char[ReplaceBufferSize];var vsb = new ValueStringBuilder(charInitSpan);
}

.net core 2.2 中,Regex的 Replace 内部用了 Span< char > 重新实现。看来,正则表达式的高性能表现 和 Span 不无关系。

根据园友的评论,Regex 以前的版本,也是通过指针来进行操作,我也实验了 .net standard的Regex , 二者效率差不多。

Span < T > 很优秀,但是为了解决 string 的性能问题,C# 早早就有了 StringBuilder 。于是我让了字符串处理界的大师:StringBuilder, 来助 Span< T > 一臂之力。

StringBuilder + Span< T >

 public string FilterCodeBlockBySpanAndStringBuilder(ReadOnlySpan<char> content){var result = new StringBuilder(content.Length);var contentSpan2 = new ReadOnlySpan<char>();var startPos = 0;var endPos = 0;var startTagSpan = _startTag.AsSpan();var endTagSpan = _endTag.AsSpan();while (true){startPos = content.IndexOf(startTagSpan);if (startPos == -1)break;contentSpan2 = content.Slice(startPos + _startTagLength, content.Length - startPos - _startTagLength);endPos = contentSpan2.IndexOf(endTagSpan);result.Append(content.Slice(0, startPos));content = contentSpan2.Slice(endPos + _endTagLength, contentSpan2.Length - endPos - _endTagLength);}result.Append(content);return result.ToString();}

将原先的 字符串拼接变成了 StringBuilder 的 append函数,而且减少了我心心念念的ToString()次数。在 .net core 2.2 中StringBuilder的内部也有 Span< T >的身影。

Append 函数可以直接接受Span< T >的参数。接下来看看武装到牙齿的Span< T >性能如何。

unbelievable ! 使用 StringBuilder 的Span< T >时间消耗居然只有 867.1微妙,内存消耗只有1.7MB ,在各个方面都技压群雄。又是百分之一的消耗。

实际上 StringBuilder的内部操作字符串的 是一个 char 数组,它的 Apend 的性能如此之高,还是因为内部使用了指针。

 			unsafe{fixed (char* valuePtr = value)fixed (char* destPtr = &chunkChars[chunkLength]){string.wstrcpy(destPtr, valuePtr, valueLen);}}

StringBuilder 只能支持字符串,但是Span< T >可是泛型的哦。不过,程序中最消耗CPU的大都是一些字符串的处理。

结语

在实际中体验了Span< T >的惊人表现。同时 .NET Core 在Span< T >加入之后,各个地方都有性能的提升,比如说Regex。真是让开发者何其幸哉。

在Regex 中的源代码,我看到了一个 ValueStringBuilder 一个内部的结构体,只能在System/Text 的内部中使用。它是一个结构体!它的构造函数可以直接传入 Span< char >,我将它 copy 出来,代替StringBuilder , 时间消耗不分伯仲,但是内存消耗又减少了一半!。这应该是极致的性能表现。鉴于篇幅原因就不展开了。

可以在 这里: https://github.com/SilentCC/MyTestBenchMarks 看到ValueStringBuilder,以及完整的代码。

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

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

相关文章

AI人工智能资料分享来袭,还不快来!

小天从大学开始&#xff0c;便开启资料收集功能。近几年以AlphaGo为契机&#xff0c;人工智能进入新的发展阶段&#xff0c;再加上日常的深入研究&#xff0c;小天收集整理了丰富的AI学习资料&#xff0c;内容涵盖“深度学习资料包”&#xff0c;“数据挖掘资料包”&#xff0c…

聊一聊Jmeter的简单使用

背景 近段时间&#xff0c;团队想补强测试这一块&#xff0c;减少重复性的一些工作&#xff0c;让一些内容可以自动化起来&#xff0c;同时对开发同学写的接口的性能也开始有所要求了。考虑到团队内没有人有测试开发的经验&#xff0c;所以前期的选择还是以工具为主&#xff0c…

win7录制系统声音 加入立体声混音 camtasia recorder录屏

很多时候&#xff0c;我们录屏的时候都并不是非得通过麦克风来说话&#xff0c;比如&#xff0c;你想跟好友分享一首歌曲的时候&#xff0c;那么你总不能把麦拿到喇叭那儿录制噻&#xff0c;那样录出来的不仅很麻烦&#xff0c;而且歌曲质量很差&#xff01;那么怎么录制系统正…

百万大奖参赛攻略 | 让程序员走向财富自由

还在担忧你的区块链项目曝光量小、品牌商业化进程慢、得不到投资人关注吗&#xff1f;这里有站上巨人肩膀的最全攻略&#xff01;2018年金链盟中国区块链应用大赛&#xff0c;开始向全国企事业单位、科研机构、项目团队、开发者队伍征集应用案例啦&#xff01;想参加的你们&…

Web通用令牌JwtBuilder

JSON Web Token (JWT)是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任&#xff0c;因为它是数字签名的。Nuget包&#xff1a;NewLife.Core、NewLife.Secrurity源码地址&a…

mysql---复杂的sql语句join的使用(left join,right join)

2019独角兽企业重金招聘Python工程师标准>>> SELECT u.*,count(u.id) AS sum FROM user AS uLEFT JOIN post AS pON p.user_id u.id RIGHT JOIN user_has_group as upON up.user_id u.id RIGHT JOIN user_has_email as ueON ue.user_id u.idWHERE u.username ! A…

你真的不了解这个地球

全世界有3.14 % 的人已经关注了数据与算法之美1. 首先来看看地球&#xff0c;看起来不错哟&#xff0c;地球&#xff5e;2. 图中圈圈里头的人口&#xff0c;比其他地区的所有总和都还要多。3. 以整个地球史来看&#xff0c;曾活过的人类高达1150亿人&#xff0c;其中包括现存的…

Squid反向代理加速缓存+负载均衡实验架构

实验环境&#xff1a; 公司有两台web服务器&#xff0c;运行同一套网站&#xff0c;读取同一台mysql数据库。 两台web服务器的主机名如下&#xff1a; test1.com 192.168.1.119 test2.com 192.168.1.120 squid服务器ip&#xff1a;192.168.1.123 DNS: 192.168.9.254 实验思路&a…

levedb 导入 mysql_LevelDB-初始篇

简介&#xff1a;LevelDB是一个基于本地文件的存储引擎&#xff0c;非分布式存储引擎&#xff0c;原理基于BigTable(LSM文件树)&#xff0c;无索引机制&#xff0c;存储条目为Key-value。适用于保存数据缓存、日志存储、高速缓存等应用&#xff0c;主要是避免RPC请求带来的延迟…

当时我就震惊了:无穷带来的各种悖论

全世界有3.14 % 的人已经关注了数据与算法之美希尔伯特旅馆悖论&#xff08;Hilberts paradox of Grand Hotel&#xff09;希尔伯特旅馆有无限个房间&#xff0c;并且每个房间都住了客人。一天来了一个新客人&#xff0c;旅馆老板说&#xff1a;“虽然我们已经客满&#xff0c;…

砸了140亿的计算机视觉,未来到底如何?

指纹解锁、刷脸识别、语音转换文字、机器人看病、Alphago我们已经深刻的感受到&#xff0c;人工智能在改变我们的工作方式和认知。通过 SAS 针对企业人工自能就绪调研的报告可以看到&#xff0c;大部分企业认为人工智能还处于初期阶段&#xff0c;“目前&#xff0c;我们正在部…

记一次 .NET WPF布草管理系统 挂死分析

一&#xff1a;背景 1. 讲故事这几天看的 dump 有点多&#xff0c;有点伤神伤脑&#xff0c;晚上做梦都是dump&#xff0c;今天早上头晕晕的到公司就听到背后同事抱怨他负责的WPF程序挂死了&#xff0c;然后测试的小姑娘也跟着抱怨。。。嗨&#xff0c;也不知道是哪一个迭代改出…

轻量级HTTP服务器Nginx(安装篇)

一、下载与安装Nginx Nginx的官方网站是http://sysoev.ru/nginx/&#xff0c;英文主页为http://nginx.net&#xff0c;从这里可以获得Nginx的最新版本信息。Nginx有三个版本&#xff1a;稳定版、开发版和历史稳定版。开发版更新较快&#xff0c;包含最新的功能和bug的修复…

中国人的数学为什么好,为什么不好

全世界有3.14 % 的人已经关注了数据与算法之美世界人民已经懒得吐槽美国学生的数学水平了&#xff0c;正如他们已习惯于惊叹中国学生的天才。脱离计算器就不会四则运算&#xff0c;把sinx/n算成“six”&#xff0c;美国学生闹的笑话层出不穷&#xff0c;每隔一段时间&#xff0…

.NET Core 调用百度 PaddleOCR 识别图文

了解 PaddleOCR 之前&#xff0c;首先了解一下 PaddlePaddle。飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础&#xff0c;是中国首个开源开放、技术领先、功能完备的产业级深度学习平台&#xff0c;集深度学习核心训练和推理框架、基础模型库、端到端开发套…

大数据时代,数据科学都有些啥?

暑假咻地一下过完啦&#xff0c;前几天&#xff0c;小天介绍了关于数模课程的开学季限时优惠&#xff08;传送门&#xff09;&#xff0c;今天要介绍的是python课程。接下来&#xff0c;小天来详细说明一下&#xff01;19月17日~23日报名《python机器学习实战》即可享受限时330…

那些被.NET大厂拒绝的大佬们,究竟弱在哪里?

如火如荼的金三银四跳槽季迎来尾声&#xff0c;几家欢喜几家愁&#xff0c;既看到腾讯阿里百度的大厂offer&#xff0c;又羡慕30k、40k、50k的高薪&#xff0c;更多其实还是各种面试失蹄的故事。成功的案例五花八门&#xff0c;而失败的原因却千篇一律。据统计&#xff0c;约70…

在python中strip_python中strip()函数怎么用?

python中strip()函数怎么用&#xff1f;发布时间&#xff1a;2020-05-19 16:57:38来源&#xff1a;亿速云阅读&#xff1a;182作者&#xff1a;Leah今天小编为大家分享的是python中strip()函数的使用方法。代码详细容易理解&#xff0c;为此分享给大家做个参考。一起跟随小编过…

有哪些经济学理论可以用在谈恋爱上?

全世界有3.14 % 的人已经关注了数据与算法之美大概两周前回加拿大的航班上&#xff0c;我在机舱尾部跟一位空姐聊了很久。大概两周前回加拿大的航班上&#xff0c;我在机舱尾部跟一位空姐聊了很久。看到浮标沉下去了&#xff0c;我却把鱼竿放回地上&#xff0c;难道我是素食主义…

python 局部变量 占内存吗_Python中全局变量和局部变量的理解与区别

前言学过编程的人应该对全局变量与局部变量这两个名词并不陌生&#xff0c;Python也同多数编程语言一样&#xff0c;也有全局变量与局部变量的概念但是与其他编程语言又有所不同全局变量与局部变量两者的本质区别就是在于作用域用通俗的话来理解的话&#xff0c;全局变量是在整…