在应用软件启动过程中,客户端应用软件是对性能敏感的。比如在解析命令行参数的时候,有时候需要进行字符串处理逻辑。一般来说命令行参数都是语言文化无关的,在需要进行全大写或全小写转换过程中,采用 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. Java原生序列化与反序列化

    序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程. 为什么需要序列化? 序列化分为两大部分:序列化和反序列化.序列化是这 ...

  2. 记录--Vue3 封装 ECharts 通用组件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 按需导入的配置文件 配置文件这里就不再赘述,内容都是一样的,主打一个随用随取,按需导入. import * as echarts from ...

  3. PostgreSQL与Java JDBC数据类型对照

    序号 数据库类型 Java类型 JDBC索引 JDBC类型 1 varchar java.lang.String 12 VARCHAR 2 char java.lang.String 1 CHAR 3 ...

  4. 修改debian apt搜索的软件包颜色(原本是绿色)

    sudo nano /etc/apt/apt.conf 加入以下内容 apt::color::highlight "#"; 再搜索软件包会变成白色 不足之处是包的前面会加上#号

  5. kubeadm init cannot find network namespace 错误

    使用 kubeadm 安装好 weave 网络插件之后,查看 kubelet 输出信息发现如下错误: 4月 25 13:51:48 k8s-master kubelet[1232730]: I0425 ...

  6. 【已解决】Exception in thread "main" java.lang.RuntimeException: java.net.ConnectException

    没有启动hadoop集群

  7. #莫比乌斯反演,杜教筛#洛谷 6055 [RC-02] GCD

    题目 分析 如果令 \(u=pj,v=qj\) ,那么本质上就是让 \(gcd(i,u,v)==1\) 那就是 \(\sum_{i=1}^n\sum_{u=1}^n\sum_{v=1}^n[gcd(i ...

  8. #dp#nssl 1478 题

    分析 设\(f[i]\)表示第\(i\)个是否幸存,\(dp[i][j]\)表示若第\(i\)个幸存,第\(j\)个是否必死 倒序枚举人,如果存在\(dp[i][a[x]],dp[i][b[x]]\) ...

  9. #莫比乌斯反演,整除分块#洛谷 6222 「P6156 简单题」加强版

    题目 多组询问,给出\(n,k\) 求 \[\sum_{i=1}^n\sum_{j=1}^n(i+j)^kgcd(i,j)\mu^2(gcd(i,j)) \] 对\(\text{unsigned}\) ...

  10. SQL ALTER TABLE 语句- 灵活修改表结构和数据类型

    SQL ALTER TABLE 语句 SQL ALTER TABLE 语句用于在现有表中添加.删除或修改列,也可用于添加和删除各种约束. ALTER TABLE - 添加列 要在表中添加列,请使用以下 ...