在应用软件启动过程中,客户端应用软件是对性能敏感的。比如在解析命令行参数的时候,有时候需要进行字符串处理逻辑。一般来说命令行参数都是语言文化无关的,在需要进行全大写或全小写转换过程中,采用 ToUpperInvariant 替换 ToUpper 方法可以避免初始化 icu 模块,减少 icu 模块初始化过慢影响启动性能

特别感谢 lsj 调查此问题和 walterlvhttps://github.com/dotnet-campus/dotnetCampus.CommandLine/pull/37 上优化命令行解析库性能

在进行 dotnet 的客户端应用启动性能分析的时候,客户端应用从逻辑上需要等待命令行参数解析完成,从而决定后续启动过程。命令行解析的性能将会影响总启动时间。在进行调查命令行解析库的性能时,发现了在命令行解析里面的某个逻辑需要对字符串转换为全大写时调用的是 ToUpper 里面传入 CultureInfo.InvariantCulture 参数方法,用来进行语言文化无关的转换大写,如以下代码

chars[0] = char.ToUpper(chars[0], CultureInfo.InvariantCulture);

以上代码将会导致在启动过程中初始化 ICU 模块,而 ICU 模块的初始化是需要耗费资源的,以下是使用 dotTrace 测量的结果

尽管 dotTrace 测量出来的 12ms 的时间是属于基本可以忽略的耗时,但是在一个以 Tick 计时的命令行解析库里面进行耗时对比,可以看到基本命令行解析所有时间都用在了 ICU 初始化上,这是不合理的

优化的方法是换成 ToUpperInvariant 从而规避 ICU 的初始化,如以下代码

chars[0] = char.ToUpperInvariant(chars[0]);

为什么这两个方法的调用会有 ICU 上的差异?原因是 ToUpper 方法里面有一个初始化判断逻辑,如 dotTrace 测量的结果图,在 IsAsciiCasingSameAsInvariant 属性里面需要进入 PopulateIsAsciiCasingSameAsInvariant 方法用来判断是否在此语言文化之下,进行大小写转换和语言文化无关是相同的结果

以下是 dotnet 运行时里面对 Char 类型的 ToUpper 方法定义,可以看到实际调用的是 CultureInfo 的 TextInfo 属性提供的 ToUpper 方法

    public readonly struct Char
{
public static char ToUpper(char c, CultureInfo culture)
{
if (culture == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
} return culture.TextInfo.ToUpper(c);
}
}

以下是 TextInfo 的 ToUpper 核心实现逻辑,代码有部分删除。通过以下代码可以看到在 ToUpper 里面需要判断一次 IsAsciiCasingSameAsInvariant 属性。在 IsAsciiCasingSameAsInvariant 属性里面只有首次需要调用到 PopulateIsAsciiCasingSameAsInvariant 方法,此方法需要执行一次判断当前语言文化进行大小写转换时和语言文化无关情况下是相同的结果

    public sealed partial class TextInfo
{
/// <summary>
/// Converts the character or string to upper case. Certain locales
/// have different casing semantics from the file systems in Win32.
/// </summary>
public char ToUpper(char c)
{
if (GlobalizationMode.Invariant)
{
return InvariantModeCasing.ToUpper(c);
} if (UnicodeUtility.IsAsciiCodePoint(c) && IsAsciiCasingSameAsInvariant)
{
return ToUpperAsciiInvariant(c);
} return ChangeCase(c, toUpper: true);
} private bool IsAsciiCasingSameAsInvariant
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_isAsciiCasingSameAsInvariant == Tristate.NotInitialized)
{
PopulateIsAsciiCasingSameAsInvariant();
} Debug.Assert(_isAsciiCasingSameAsInvariant == Tristate.True || _isAsciiCasingSameAsInvariant == Tristate.False);
return _isAsciiCasingSameAsInvariant == Tristate.True;
}
} [MethodImpl(MethodImplOptions.NoInlining)]
private void PopulateIsAsciiCasingSameAsInvariant()
{
bool compareResult = CultureInfo.GetCultureInfo(_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", CompareOptions.IgnoreCase) == 0;
_isAsciiCasingSameAsInvariant = (compareResult) ? Tristate.True : Tristate.False;
} private Tristate _isAsciiCasingSameAsInvariant = Tristate.NotInitialized;
}

虽然这里传入的是 CultureInfo.InvariantCulture 语言文化无关,但是 TextInfo 层不知道,还是需要跑一次 PopulateIsAsciiCasingSameAsInvariant 判断逻辑。这个判断逻辑里面需要初始化 ICU 模块

而调用 Char 的 ToUpperInvariant 则是走完全的静态的 TextInfo 的 ToUpperInvariant 方法,如以下代码

    public readonly struct Char
{
public static char ToUpperInvariant(char c) => TextInfo.ToUpperInvariant(c);
}

在静态的 TextInfo 的 ToUpperInvariant 方法是明确知道语言文化无关的,不需要进行任何判断逻辑,如此即可减少 ICU 模块初始化,在启动时调用的速度将会更快

    public sealed partial class TextInfo
{
internal static char ToUpperInvariant(char c)
{
if (GlobalizationMode.Invariant)
{
return InvariantModeCasing.ToUpper(c);
} if (UnicodeUtility.IsAsciiCodePoint(c))
{
return ToUpperAsciiInvariant(c);
} return Invariant.ChangeCase(c, toUpper: true);
}
}

值得一提的是本文所讲的性能差异仅仅只是在应用启动过程中有效,如果不是应用启动过程,基本上 ICU 也初始化过了,不会存在耗时问题,而且非性能敏感的逻辑也不会有如此严格的耗时要求

相同的启动问题性能优化,也在 MAUI 仓库里面执行,在 MAUI 里面引入了 CA1311: Specify a culture or use an invariant version 警告提示,意思就是如果发现代码里面写了不带语言文化的 String.ToUpper()String.ToLower() 方法,将会提示换成 ToUpper(CultureInfo)ToUpperInvariant()ToLower(CultureInfo)ToLowerInvariant() 方式减少语言文化加载性能

如在 .NET 8 Performance Improvements in .NET MAUI - .NET Blog 博客里面提到的

Address CA1307 and CA1309 for performance

Profiling a .NET MAUI sample application from a customer, we noticed time spent during “culture-aware” string operations:

77.22ms microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

42.55ms System.Private.CoreLib!System.String.ToLower()

This case, we can improve by simply calling ToLowerInvariant() instead. In some cases you might even consider using string.Equals() with StringComparer.Ordinal.

更改的代码如下

https://github.com/dotnet/maui/pull/14627

通过 ToLowerInvariant 和 ToUpperInvariant 转换大小写等方法代替引入语言文化相关的判断,在逻辑等价变更的情况下,可以减少启动耗时

dotnet 提升 ToUpper 性能的更多相关文章

  1. dotnet 启动 JIT 多核心编译提升启动性能

    用2分钟提升十分之一的启动性能,通过在桌面程序启动 JIT 多核心编译提升启动性能 在 dotnet 可以通过让 JIT 进行多核心编译提升软件的启动性能,在默认托管的 ASP.NET 程序是开启的, ...

  2. dotnet 使用 Crossgen2 对 DLL 进行 ReadyToRun 提升启动性能

    我对几个应用进行严格的启动性能评估,对比了在 .NET Framework 和 dotnet 6 下的应用启动性能,非常符合预期的可以看到,在用户的设备上,经过了 NGen 之后的 .NET Fram ...

  3. 2019-8-31-dotnet-启动-JIT-多核心编译提升启动性能

    title author date CreateTime categories dotnet 启动 JIT 多核心编译提升启动性能 lindexi 2019-08-31 16:55:58 +0800 ...

  4. [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能

    [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能 本节导读: 上节说了缓存是以空间来换取时间的技术,介绍了客户端缓存和两种常用服务器缓布,本节主要介绍一种. ...

  5. 提升PHP性能的21种方法

    提升PHP性能的21种方法. 1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成st ...

  6. HHVM 是如何提升 PHP 性能的?

    背景 HHVM 是 Facebook 开发的高性能 PHP 虚拟机,宣称比官方的快9倍,我很好奇,于是抽空简单了解了一下,并整理出这篇文章,希望能回答清楚两方面的问题: HHVM 到底靠谱么?是否可以 ...

  7. 使用异步HTTP提升客户端性能(HttpAsyncClient)

    使用异步HTTP提升客户端性能(HttpAsyncClient) 大家都知道,应用层的网络模型有同步.异步之分. 同步,意为着线程阻塞,只有等本次请求全部都完成了,才能进行下一次请求. 异步,好处是不 ...

  8. 极光开发者沙龙 之 移动应用性能优化实践 【一】旧酒新瓶——换个角度提升 App 性能与质量

    旧酒新瓶--换个角度提升 App 性能与质量 主讲人:高亮亮 ---   饿了么移动技术部高级iOS工程师,负责饿了么商家版iOS APP开发,对架构和系统底层有深入研究,擅长移动性能分析,troub ...

  9. React爬坑秘籍(一)——提升渲染性能

    React爬坑秘籍(一)--提升渲染性能 ##前言 来到腾讯实习后,有幸八月份开始了腾讯办公助手PC端的开发.因为办公助手主推的是移动端,所以导师也是大胆的让我们实习生来技术选型并开发,他来做code ...

  10. 十个技巧迅速提升JQuery性能

    本文提供即刻提升你的脚本性能的十个步骤.不用担心,这并不是什么高深的技巧.人人皆可运用!这些技巧包括: 使用最新版本 合并.最小化脚本 用for替代each 用ID替代class选择器 给选择器指定前 ...

随机推荐

  1. Android组件化开发实践和案例分享

    目录介绍 1.为什么要组件化 1.1 为什么要组件化 1.2 现阶段遇到的问题 2.组件化的概念 2.1 什么是组件化 2.2 区分模块化与组件化 2.3 组件化优势好处 2.4 区分组件化和插件化 ...

  2. FreeRTOS教程8 任务通知

    1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP仿真器 XCO ...

  3. Button按钮Effect的用法

    教大家写一个好看的Button按钮 代码简单粗暴 <Grid > <Border Width="200" Height="30" Margin ...

  4. 美团一面:说一说Java中的四种引用类型?

    引言 在JDK1.2之前Java并没有提供软引用.弱引用和虚引用这些高级的引用类型.而是提供了一种基本的引用类型,称为Reference.并且当时Java中的对象只有两种状态:被引用和未被引用.当一个 ...

  5. NFNet:NF-ResNet的延伸,不用BN的4096超大batch size训练 | 21年论文

    论文认为Batch Normalization并不是网络的必要构造,反而会带来不少问题,于是开始研究Normalizer-Free网络,希望既有相当的性能也能支持大规模训练.论文提出ACG梯度裁剪方法 ...

  6. 探索多种数据格式:JSON、YAML、XML、CSV等数据格式详解与比较

    1. 数据格式介绍 数据格式是用于组织和存储数据的规范化结构,不同的数据格式适用于不同的场景.常见的数据格式包括JSON.YAML.XML.CSV等. 数据可视化 | 一个覆盖广泛主题工具的高效在线平 ...

  7. Linux 基础命令和帮助命令

    Linux命令 X Window 与命令行模式的切换   Linux默认的情况下会提供六个终端来让用户登录,切换的方式为使用[Ctrl+Alt+F1~F6]的组合键.系统会将[F1 ~ F6]命名为t ...

  8. #线段树,约数#洛谷 3889 [GDOI2014]吃

    题目 有一个长度为\(n\)的数列,现在有\(m\)组询问每次给出区间\([l,r]\),查询 \[\max_{i,j=1}^n\{gcd(a_i,a_j)[(i<l或i>r)且l\leq ...

  9. OpenHarmony——内核对象队列之算法详解(下)

    前言 OpenAtom OpenHarmony(以下简称"OpenHarmony") LiteOS-M 内核是面向 IoT 领域构建的轻量级物联网操作系统内核,具有小体积.低功耗. ...

  10. node nvm使用

    背景 node 经过了一次大的改变,直接从8到了10,差别很大,但是有的项目又需要用到8,这个时候不能完全升级. 所以我们需要一个管理虚拟环境的工具. 安装 https://github.com/co ...