范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

记一次。NET程序的性能优化实战(3)深入。NET源码

  前言
  前两篇文章 part1 和 part2 基本上理清了  IsSplitter()  运行缓慢的原因 —— 在函数内部使用了带 Compile  选项的正则表达式。
  但是没想到在  IsSplitter()  内部使用不带 Compiled  选项的正则表达式,整个程序运行起来非常快,跟静态函数版本的运行速度不相上下。又有了如下疑问:为什么使用不带  Compiled  选项实例化的 Regex  速度会这么快?为什么把  Regex  变量从局部改成全局变量后运行速度有了极大提升?除了避免重复实例化,还有哪些提升?为什么  PerfView  收集到的采样数据,大部分发生在 MatchCollections.Count  内部,极少发生在 Regex  的构造函数内部?(使用带 Compiled  选项的正则表达式的时候)Regex.IsMatch()  是如何使用缓存的?直接实例化的  Regex  对象会使用正则表达式引擎内部的缓存吗?正则表达式引擎内部根据什么缓存的? 什么时候会生成动态方法?生成的动态方法是在哪里调用的?
  本文会继续使用  Perfview  抓取一些关键数据进行分析,有些疑问需要到 .NET  源码中寻找答案。在查看代码的过程中,发现有些逻辑单纯看源码不太容易理解,于是又调试跟踪了 .NET  中正则表达式相关源码。由于篇幅原因,本篇不会介绍如何下载 .NET  源码,如何调试 .NET  源码的方法。但是会单独写一篇简单的介绍文章 。解惑为什么使用不带  Compiled  选项实例化的 Regex  速度会这么快?还是使用  PerfView  采集性能数据并分析,如下图:可以发现,  IsSplitter()  函数只在第一次被调用时发生了一次 JIT ,后续调用耗时不到 0.1ms (图中最后一次调用耗时:4090.629-4090.597 = 0.032ms )。使用带  Compiled  选项实例化的 Regex  的 IsSplitter()  函数,如下图:view-filter-event-with-etwlogger 每次调用大概要消耗  11ms  (5616.375 - 5604.637 = 11.738 ms )。至于为什么不带  Compiled  选项的正则表达式在调用过程中没有多余的 JIT ,与疑问7 一起到源码中找答案。为什么把  Regex  变量从局部改成全局变量后运行速度有了极大提升?除了避免重复实例化,还有哪些提升?修改代码,把局部变量改成全局变量,编译。再次使用  PerfView  采集性能数据并分析,如下图:可以发现与使用不带  Compiled  选项的局部变量版本一样,只发生了一次 JIT 。所以把局部变量改成全局变量后,除了避免了重复实例化的开销(很小),更重要的是避免了多余的 JIT  操作。为什么  PerfView  收集到的采样数据,大部分发生在 MatchCollections.Count  内部,极少发生在 Regex  的构造函数内部?(使用带 Compiled  选项的正则表达式的时候)Regex  构造函数只被 JIT  了一次,后面的调用都是在执行原生代码,执行速度非常快。而 MatchCollections.Count  每次执行的时候都需要执行 JIT (每次都需要 10ms  以 上),所以大部分数据在 MatchCollections.Count  内部,是非常合理的。Regex.IsMatch()  是如何使用缓存的?Regex.IsMatch()  有很多重载版本,最后都会调用下面的版本:static    bool    IsMatch  (String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
  return    new   Regex(pattern, options, matchTimeout,  true  ).IsMatch(input);
  }
  该函数会在内部构造一个临时的  Regex  对象,并且构造函数的最后一个参数 useCaChe  的值是 true ,表示使用缓存。
  疑问5  和  疑问6  的答案在  Regex  的构造函数中,先看看 Regex  的构造函数。Regex 构造函数
  Regex  有很多个构造函数,列举如下:public Regex(String pattern)   : this(pattern, RegexOptions.None, DefaultMatchTimeout, false) {}    public Regex(String pattern, RegexOptions options)   : this(pattern, options, DefaultMatchTimeout, false) {}          Regex(String pattern, RegexOptions options, TimeSpan matchTimeout)   : this(pattern, options, matchTimeout, false) {}
  注意:   以上构造函数的最后一个参数都是   false  ,表示不使用缓存。
  这些构造函数最后都会调用下面的 私有 构造函数(代码有所精简调整): private Regex(String pattern, RegexOptions options, TimeSpan matchTimeout, bool useCache) {   string cultureKey = null;   if ((options & RegexOptions.CultureInvariant) != 0)       cultureKey = CultureInfo.InvariantCulture.ToString(); // "English (United States)"   else       cultureKey = CultureInfo.CurrentCulture.ToString();      // 构造缓存用到的 key,包含 options,culture 和 pattern   String key = ((int) options).ToString(NumberFormatInfo.InvariantInfo) + ":" + cultureKey + ":" + pattern;   CachedCodeEntry cached = LookupCachedAndUpdate(key);    this.pattern = pattern;   this.roptions = options;    if (cached == null) {       // 如果没找到缓存就生成类型为 RegexCodes 的 code,包含了字节码等信息       RegexTree tree = RegexParser.Parse(pattern, roptions);       code = RegexWriter.Write(tree);       // 如果指定了 useCache 参数就缓存起来,下次就能在缓存中找到了       if (useCache)           cached = CacheCode(key);   } else {       // 如果找到了缓存就使用缓存中的信息       code       = cached._code;       factory    = cached._factory;       runnerref  = cached._runnerref;   }    // 如果指定了 Compiled 选项,并且 factory 是空(没使用缓存,或者缓存中的 _factory 是空)   if (UseOptionC() && factory == null) {       // 根据 code 和 roptions 生成 factory       factory = Compile(code, roptions);          // 需要缓存就缓存起来       if (useCache && cached != null)           cached.AddCompiled(factory);   } }
  注意:   带   bool useCache   标记的构造函数是私有的,也就是说不能直接使用此构造函数实例化  Regex  。
  首先会根据  option + culture + pattern  到缓存中查找。如果没找到缓存就生成类型为 RegexCodes  的 code (包含了字节码等信息),如果找到了缓存就使用缓存中的信息。 如果指定了 Compiled  选项(UseOptionC()  会返回 true ),并且 factory  是空(没使用缓存或者缓存中的 _factory  是空),就会执行 Compile()  函数,并把返回值保存到 factory  成员中。
  至此,可以回答第  5 6  两个疑问了。直接实例化的  Regex  对象会使用正则表达式引擎内部的缓存吗?会优先根据  option + culture + pattern  到缓存中查找 ,但是否更新缓存 是由最后一个参数 useCache  决定的,与是否指定 Compiled  选项无关。正则表达式引擎内部根据什么缓存的? 根据  option + culture + pattern  缓存。
  疑问7  与由  疑问1  引申出来的  JIT  问题是一个问题。之所以会 JIT ,是因为有需要 JIT  的代码,如果不断有新的动态方法产生出来并执行,那么就需要不断地 JIT 。由于此问题涉及到的代码量比较大,逻辑比较复杂,需要深入 .NET  源码进行查看。为了更好的理解整个过程,我简单梳理了 IsSpitter()  函数中涉及到的关键类以及类之间的关系,整理成下图,供参考。流程 & 类关系梳理
  看完上图后,可以继续看剩下的  JIT  问题了。因为大多数 JIT  都出现在 MatchCollection.Count  中,可以由此切入。MatchCollection.Count
  实现代码如下: public int Count {   get {     if (_done)       return _matches.Count;     GetMatch(infinite);     return _matches.Count;   } }
  Count  会调用 GetMatch()  函数,而 GetMatch()  函数会不断调用 _regex.Run()  函数。
  _regex  是哪来的呢?在构造 MatchCollection  实例时传过来的。
  MatchCollection  是由 Regex.Matches()  实例化的,代码如下(去掉了判空逻辑):public MatchCollection Matches(String input, int startat) {   return new MatchCollection(this, input, 0, input.Length, startat); }
  该函数会实例化一个  MatchCollection  对象,并把当前 Regex  实例作为第一个参数传给 MatchCollection  的构造函数。该参数会被保存到 MatchCollection  实例的 _regex  成员中。
  接下来继续查看  Regex.Run  函数的实现。Regex.Run()
  具体实现代码如下(代码有精简): internal Match Run(bool quick, int prevlen, String input, int beginning, int length, int startat) {     Match match;     // 使用缓存的时候,可能从缓存中拿到一个有效的 runner,其它情况下都是 null。     RegexRunner runner = (RegexRunner)runnerref.Get();      // 不使用缓存的时候 runner是 null     if (runner == null) {         // 如果 factory 不为空就通过 factory 创建一个 runner。         // 使用了 Compiled 标志创建的 Regex 实例的 factory 不为空         if (factory != null)             runner = factory.CreateInstance();         else             runner = new RegexInterpreter(code, UseOptionInvariant() ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);     }      try {         // 调用 RegexRunner.Scan 扫描匹配项。         match = runner.Scan(this, input, beginning, beginning + length, startat, prevlen, quick, internalMatchTimeout);     } finally {         runnerref.Release(runner);     }      return match; }
  逻辑还是非常清晰的,先找到或者创建(通过  factory.CreateInstance()  或者直接 new )一个类型为 RegexRunner  实例 runner ,然后调用 runner->Scan()  进行匹配。
  对于使用  Compiled  选项创建的 Regex ,其 factory  成员变量会在 Regex  构造函数中赋值,对应的语句是 factory = Compile(code, roptions);  ,类型是 CompiledRegexRunnerFactory 。
  我们先来看看  CompiledRegexRunnerFactory.CreateInstance()  的实现。CompiledRegexRunnerFactory.CreateInstance()
  代码如下: protected internal override RegexRunner CreateInstance() {   CompiledRegexRunner runner = new CompiledRegexRunner();    new ReflectionPermission(PermissionState.Unrestricted).Assert();   // 设置关键的动态函数,这三个函数是在 `RegexLWCGCompiler`   // 类的 `FactoryInstanceFromCode()` 中生成的。   runner.SetDelegates(     (NoParamDelegate) goMethod.CreateDelegate(typeof(NoParamDelegate)),     (FindFirstCharDelegate) findFirstCharMethod.CreateDelegate(typeof(FindFirstCharDelegate)),     (NoParamDelegate) initTrackCountMethod.CreateDelegate(typeof(NoParamDelegate))   );    return runner; }
  该函数返回的是  CompiledRegexRunner  类型的 runner 。在返回之前会先调用 runner.SetDelegates  为对应的关键函数(Go , FindFirstChar , InitTrackCount )赋值。参数中的 goMethod, findFirstCharMethod, initTrackCountMethod  是在哪里赋值的呢?在 Regex.Compile()  函数中赋值的。Regex.Compile()
  Regex.Compile()  会直接转调 RegexCompiler  的静态函数 Compile() ,相关代码如下(有调整):internal static RegexRunnerFactory Compile(RegexCode code, RegexOptions options) {   RegexLWCGCompiler c = new RegexLWCGCompiler();   return c.FactoryInstanceFromCode(code, options); }
  该函数直接调用了  RegexLWCGCompiler  类的 FactoryInstanceFromCode()  成员函数。相关代码如下(有删减):internal RegexRunnerFactory FactoryInstanceFromCode(RegexCode code, RegexOptions options) {    // 获取唯一标识符,也就是FindFirstChar后面的数字   int regexnum = Interlocked.Increment(ref _regexCount);   string regexnumString = regexnum.ToString(CultureInfo.InvariantCulture);    // 生成动态函数Go   DynamicMethod goMethod = DefineDynamicMethod("Go" + regexnumString, null, typeof(CompiledRegexRunner));   GenerateGo();    // 生成动态函数FindFirstChar   DynamicMethod firstCharMethod = DefineDynamicMethod("FindFirstChar" + regexnumString, typeof(bool), typeof(CompiledRegexRunner));   GenerateFindFirstChar();      // 生成动态函数InitTrackCount     DynamicMethod trackCountMethod = DefineDynamicMethod("InitTrackCount" + regexnumString, null, typeof(CompiledRegexRunner));   GenerateInitTrackCount();    return new CompiledRegexRunnerFactory(goMethod, firstCharMethod, trackCountMethod); }
  该函数非常清晰易懂,但却是 非常关键 的一个函数,会生成三个动态函数(也就是通过  PerfView  采集到的 FindFirstCharXXX ,GoXXX ,InitTrackCountXXX ),最后会构造一个类型为 CompiledRegexRunnerFactory  的实例,并把生成的动态函数作为参数传递给 CompiledRegexRunnerFactory  的构造函数。
  至此,已经找到 生成动态函数 的地方了。动态函数是什么时候被调用的呢?在  runner.Scan()  函数中被调用的。RegexRunner.Scan()
  关键代码如下(做了大量删减): Match Scan(Regex regex, String text, int textbeg, int textend, int textstart, int prevlen, bool quick, TimeSpan timeout) {   for (; ; ) {     if (FindFirstChar()) {       Go();        if (runmatch._matchcount [0] > 0)         return TidyMatch(quick);     }   } }
  可以看到, Scan()  函数内部会调用 FindFirstChar()  和 Go() ,而且只有当 FindFirstChar()  返回 true  的时候,才会调用 Go() 。这两个函数是虚函数,具体的子类会重写。对于 Compiled  类型的正则表达式,对应的 runner  类型是 CompiledRegexRunner 。这三个关键的函数实现如下:internal sealed class CompiledRegexRunner : RegexRunner {   NoParamDelegate goMethod;   FindFirstCharDelegate findFirstCharMethod;   NoParamDelegate initTrackCountMethod;            protected override void Go() {     goMethod(this);   }    protected override bool FindFirstChar() {     return findFirstCharMethod(this);   }    protected override void InitTrackCount() {     initTrackCountMethod(this);   } }
  现在可以回答 疑问7  及 疑问1  引申出来的  JIT  问题了。什么时候会生成动态方法?生成的动态方法是在哪里调用的? 在指定了  Compiled  标志的 Regex  的构造函数内部会调用 RegexCompiler.Compile()  函数,Compile()  函数又会调用 RegexLWCGCompiler.FactoryInstanceFromCode() ,FactoryInstanceFromCode()  函数内部会分别调用 GenerateFindFirstChar() , GenerateGo() , GenerateInitTrackCount()  生成对应的动态方法。在执行  MatchCollection.Count  的时候,会调用 MatchCollection.GetMatch()  函数,GetMatch()  函数会调用对应 RegexRunner  的 Scan()  函数。Scan()  函数会调用 RegexRunner.FindFirstChar() ,而 CompiledRegexRunner  类型中的 FindFirstChar()  函数调用的是设置好的动态函数。Compiled 与 非 Compiled 对比1. 构造函数
  带  Compiled  选项的 Regex
  useCache  传递的是 false ,表示不使用缓存。因为指定了 RegexOptions.Compiled  选项, Regex  的构造函数内部会调用 RegexCompiler.Compile()  函数,Compile()  函数又会调用 RegexLWCGCompiler.FactoryInstanceFromCode() ,FactoryInstanceFromCode()  函数内部会分别调用 GenerateFindFirstChar() , GenerateGo() , GenerateInitTrackCount()  生成对应的动态方法,然后返回 CompiledRegexRunnerFactory  类型的实例。如下图:
  compiled-regex-constructor
  不带  Compiled  选项的 Regex
  构造函数与  Compiled  的基本一致,useCache  传递的也是 false ,不使用缓存。因为 UseOptionC()  返回的是 false ,所以不会执行 Compile()  函数。所以 factory  成员变量是 null 。
  这里就不贴图了。 2. matches.Count
  带  Compiled  选项的 Regex
  MatchCollection-count-dynamic-FindFirstChar
  MatchCollection.Count  内部会调用 GetMatch()  函数,GetMatch()  函数会调用对应 RegexRunner  的 Scan()  函数(这里的 runner  类型是 CompiledRegexRunner )。Scan()  内部会调用 FindFirstChar()  函数,而 CompiledRegexRunner  类型的 FindFirstChar()  函数内部调用的是设置好的动态方法。
  不带  Compiled  选项的 Regex
  MatchCollection-count-none-dynamic-FindFirstChar
  与带  Compiled  版本的调用栈基本一致,不一样的是这里 runner  的类型是 RegexInterpreter ,该类型的 FindFirstChar()  函数调用的代码不是动态生成的。3. runner 赋值
  当  runner  是 null  的时候,需要根据情况获取对应的 runner 。
  带  Compiled  选项的 Regex
  factory  成员在 Regex  构造函数里通过 Compile()  赋过值,runner  会通过下图 1306  行的 factory.CreateInstance()  赋值。
  不带  Compiled  选项的 Regex
  factory  成员没有被赋过值,因此是空的,runner  会通过下图 1308  行的 new RegexInterpreter()  赋值。
  runner 总结不要在循环内部创建编译型的正则表达式(带  Compiled  选项),会频繁导致 JIT  的发生进而影响效率。Regex.IsMatch()  也会创建 Regex 实例,但是最后一个参数 bUseCache  是 true ,表示使用缓存。Regex  构造函数的最后一个参数 bUseCache  是 true  的时候才会更新缓存。正则表达式引擎内部会根据  option + culture + pattern  查找缓存。参考资料
  .NET源码

祝贺!中国科学院院士王立军加入亚太人工智能学会!近日,中国科学院院士中国科学院长春光学精密机械与物理研究所研究员王立军院士加入亚太人工智能学会(以下简称AAIA)。1hr职业生涯王立军,1946年出生于吉林省舒兰县,1970年进IDC发布中国智能家居市场十大预测!2022年出货量将达2。2亿台智东西作者程茜编辑心缘智东西1月11日报道,今天,全球市研机构IDC发布预测,2022年中国智能家居设备出货量将达到2。2亿台,同比持平微涨,家庭安全监控和智能照明将成为未来几年引联邦快递新加坡转运中心引入人工智能分拣机器人,推动运营效率提升该分拣机器人分拣效率高达每小时1000个包裹,最高可承载5公斤的包裹,最多可同时操作100个目的地流向。1月10日,联邦快递集团旗下附属公司兼全球最具规模的速递运输公司之一联邦快递MIUI14真的好用!小米11Ultra发热改善,现在真是一款神机MIUI14这个系统可以说是真的流量太高了,之前系统还没有发布的时候,就被网友们吹上了天,当然,我自己也是非常期待这个系统可以变得流畅一些,所以我的小米11Ultra推送了MIUI三星GalaxyA34A54外观配置曝光搭载Exynos芯片据GSMArena报道,EvanBlass分享了两张三星GalaxyA系列产品外观图,预计是即将发布的GalaxyA34和GalaxyA54新品。从曝光的图片看,三星GalaxyAiPhone14再翻车,灵动岛问题频发,小米11售后获人民网点赞iPhone14系列一出来就受到人们的热烈追捧,灵动岛是这次硬件上创新玩出的新花样,仅仅一个灵动岛,就让果粉玩的不亦乐乎。不过一个月后iPhone14Pro出现的问题还是不少,这也这8个隐藏极深的小米手机技巧,5年米粉都不一定全知道,超实用今天想和大家分享8个小米手机藏得极深的技巧,5年米粉都不一定全知道,学会这8招,让你的小米手机更好用!桌面无字模式有些朋友喜欢干净的手机页面,并不喜欢桌面图标的文字,其实我们可以让小米推出首款迷你主机,但为何带火了零刻SEi12Pro?前段时间,小米推出了自家首款迷你电脑主机。对于这款迷你主机,网友们的评价可谓是褒贬不一,有说它是贴牌的,也有夸赞的。在我个人看来,当一个品牌进入新的领域,必然会有各种诸多不足逐渐暴库里被告上法庭!FTX平台宣布破产!损失110亿美元!代言人被起诉前些日子,技巧君曾写过这么一篇报道,内容是由库里作为代言人的FTX平台宣布破产,大量投资者损失惨重,向法院提起诉讼。在这起诉讼案中,以库里奥尼尔汤姆布雷迪为首的几位全球代言人均被列什么是PCDN,普通用户如何参与?一PCDN是什么?可能有的玩家对PCDN还不了解,其实PCDN就一种基于P2P技术的内容分发网络。PCDN是广泛应用于视频行业的内容分发加速网络,其基本原理是广泛采用各种缓存服务器微信切断抖音链接分享,两大平台竞争矛盾升级?金庸曾写过有人的地方就有恩怨,有恩怨的地方就有江湖,这句话放在商业世界也同同样适用,有公司的地方就有竞争,有竞争的地方就有纷争。尤其是在中国这么大的经济体和市场上,公司与公司之间的
新规改造满6个月信用卡助力消费复苏本报记者张漫游北京报道2022年7月关于进一步促进信用卡业务规范健康发展的通知(以下简称通知或信用卡新规)落地,其中提到,各银行信用卡部门需按照要求完成业务流程及系统改造等工作,期共享合作发展新机遇来源人民网在老挝36庄园,技术人员在生产车间加工茶叶。老挝36庄园供图在西班牙马德里的一家超市,工作人员在工作。本报记者许海林摄巴西米奥罗葡萄酒庄园一瞥。鲁本斯卢西奥摄随着中国的对天上掉下个大霹雷,但为何更该见猎心喜电影手机里面的经典台词,没事还是要打把伞的,不晓得哪片云彩有雨。本日上半场是众口相声吹牛逼时间,下午就立马遭遇雷劈电打,收盘数据显示,有600亿资金应声而逃。若要问为什么,没有什么清远首笔数字人民币缴纳税费业务成功落地2月15日,忠华集团有限公司财务人员张小姐拿到了全市第一张使用数字人民币账户缴纳税款和非税收入的完税凭证,这标志着清远市首笔数字人民币缴纳税费业务成功落地。使用数字人民币缴纳税费,收盘丨创业板指大跌2。51电信运营软件等板块跌幅居前2月17日,三大股指低开低走,创业板指走势较弱。截至收盘,沪指跌0。77,深证成指跌1。61,创业板指跌2。51。总体上个股跌多涨少,两市超2800只个股下跌。沪深两市今日成交额9美国不再是百万富翁定居首选英国调查报告显示,美国不再是百万富翁的首选移居国家。图为美国富豪度假胜地迈阿密棕榈滩。(彭博社)(纽约彭博电)美国曾经是百万富翁的首选移居国家,但如今美国却失去了这份吸引力。英国投中粮集团在宁夏银川投资30亿元布局现代养殖全产业链项目中新网银川2月17日电(记者李佩珊)日前,中粮集团与宁夏银川灵武市政府签订战略投资合作协议。中粮集团计划在灵武市投资30亿元,围绕当地现代养殖产业绿色食品加工产业布局规划,建设集饲1000亿颗芯片订单,台积电重启南京工厂,中芯国际面临压力大家都知道台积电虽然是一家中国企业,但是一直受制于美国,因为台积电的技术人才专利大部分来源于美国,特别是光刻机的核心技术专利,并且美国拥有台积电20以上的股份,是第一大股东,台积电(经济)山东文登外贸企业新春生产忙早春时节,山东省威海市文登区众多外贸企业开足马力赶制订单,当地政府制定出台多项优化营商环境政策,帮助企业解决用工融资等实际困难,助力企业实现2023年首季开门红。2月16日,员工在山西振东制药股份有限公司技术中心被认定为国家企业技术中心2月3日,国家公布了2022年国家企业技术中心名单,山西振东制药股份有限公司技术中心被认定为国家企业技术中心,是山西省2022年唯一通过国家认定的企业技术中心。国家企业技术中心是由全力以赴领航未来珍岛集团获评TMA年度最具影响力移动营销公司1月6日,珍岛集团经过专家评审团策略创意媒介应用技术支持效果转化等多个角度的考核,获选第九届TMA(TopMobileAwards)移动营销大奖年度最具影响力移动营销公司,成为推动