dotnet 提升 ToUpper 性能
在应用软件启动过程中,客户端应用软件是对性能敏感的。比如在解析命令行参数的时候,有时候需要进行字符串处理逻辑。一般来说命令行参数都是语言文化无关的,在需要进行全大写或全小写转换过程中,采用 ToUpperInvariant 替换 ToUpper 方法可以避免初始化 icu 模块,减少 icu 模块初始化过慢影响启动性能
特别感谢 lsj 调查此问题和 walterlv 在 https://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 性能的更多相关文章
- dotnet 启动 JIT 多核心编译提升启动性能
用2分钟提升十分之一的启动性能,通过在桌面程序启动 JIT 多核心编译提升启动性能 在 dotnet 可以通过让 JIT 进行多核心编译提升软件的启动性能,在默认托管的 ASP.NET 程序是开启的, ...
- dotnet 使用 Crossgen2 对 DLL 进行 ReadyToRun 提升启动性能
我对几个应用进行严格的启动性能评估,对比了在 .NET Framework 和 dotnet 6 下的应用启动性能,非常符合预期的可以看到,在用户的设备上,经过了 NGen 之后的 .NET Fram ...
- 2019-8-31-dotnet-启动-JIT-多核心编译提升启动性能
title author date CreateTime categories dotnet 启动 JIT 多核心编译提升启动性能 lindexi 2019-08-31 16:55:58 +0800 ...
- [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能
[.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能 本节导读: 上节说了缓存是以空间来换取时间的技术,介绍了客户端缓存和两种常用服务器缓布,本节主要介绍一种. ...
- 提升PHP性能的21种方法
提升PHP性能的21种方法. 1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成st ...
- HHVM 是如何提升 PHP 性能的?
背景 HHVM 是 Facebook 开发的高性能 PHP 虚拟机,宣称比官方的快9倍,我很好奇,于是抽空简单了解了一下,并整理出这篇文章,希望能回答清楚两方面的问题: HHVM 到底靠谱么?是否可以 ...
- 使用异步HTTP提升客户端性能(HttpAsyncClient)
使用异步HTTP提升客户端性能(HttpAsyncClient) 大家都知道,应用层的网络模型有同步.异步之分. 同步,意为着线程阻塞,只有等本次请求全部都完成了,才能进行下一次请求. 异步,好处是不 ...
- 极光开发者沙龙 之 移动应用性能优化实践 【一】旧酒新瓶——换个角度提升 App 性能与质量
旧酒新瓶--换个角度提升 App 性能与质量 主讲人:高亮亮 --- 饿了么移动技术部高级iOS工程师,负责饿了么商家版iOS APP开发,对架构和系统底层有深入研究,擅长移动性能分析,troub ...
- React爬坑秘籍(一)——提升渲染性能
React爬坑秘籍(一)--提升渲染性能 ##前言 来到腾讯实习后,有幸八月份开始了腾讯办公助手PC端的开发.因为办公助手主推的是移动端,所以导师也是大胆的让我们实习生来技术选型并开发,他来做code ...
- 十个技巧迅速提升JQuery性能
本文提供即刻提升你的脚本性能的十个步骤.不用担心,这并不是什么高深的技巧.人人皆可运用!这些技巧包括: 使用最新版本 合并.最小化脚本 用for替代each 用ID替代class选择器 给选择器指定前 ...
随机推荐
- vue3.0后多环境配置
根目录下创建 .env 每个配置文件中都将包含此文件中的数据,类似于配置文件的全局 .env.development 默认开发环境 对应serve .env.production 默认生产环境 对应b ...
- AABO:自适应Anchor设置优化,性能榨取的最后一步 | ECCV 2020 Spotlight
论文提出超参数优化方法AABO,该方法核心基于贝叶斯优化和Sub-Sample方法,能够自适应的搜索最优的anchor设置.从实验结果来看,AABO能够仅通过anchor设置优化,为SOTA目标检测方 ...
- 实际项目中如何使用Git做分支管理
前言 Git是一种强大的分布式版本控制系统,在实际项目开发中使用Git进行分支管理是非常常见的做法,因为它可以帮助团队高效的协作和管理项目的不同版本,今天我们来讲讲在实际项目中最常用的Git分支管理策 ...
- 详解SSL证书系列(9)SSL客户端认证
上一篇介绍了HTTPS和HTTP协议的区别,理解了HTTP加上加密处理和认证以及完整性保护后即是HTTPS,同时HTTPS也是身披SSL外壳的HTTP,那么SSL客户端认证是怎么回事呢?这篇文章我将带 ...
- #树形dp,二分#UVA1407 Caves 洛谷 3698 [CQOI2017]小Q的棋盘
题目 给定一棵树,现在需要找到一条由节点1出发长度为\(x\)的路径, 问最多经过的节点数,重复经过只计算一次(不一定是简单路径) UVA的那道题多组数据多组询问,边权还不一定是1,\(n\leq 5 ...
- #计数,记忆化搜索#C 连边方案
分析 设\(dp[i][j][k][l]\)表示处理到\([i-l+1,i]\)的连边,二进制状态(奇点还是偶点)为\(k\)的方案数, 最后一维是为了避免算重,那么如果第\(i-l+1\)位是偶点可 ...
- Jetty的工作参数
Jetty加载参数的优先级: 命令行. $JETTY_BASE下的文件. 使用--include-jetty-dir指定的目录下的文件. $JETTY_HOME下的文件. 查看当前的配置参数,执行如下 ...
- JDK 14的新特性:switch表达式
目录 简介 写在前面 连写case switch返回值 yield 总结 简介 switch的新特性可是源远流长,早在JDK 12就以预览功能被引入了,最终在JDK 14成为了正式版本的功能:JEP ...
- OpenHarmony社区运营报告(2023年2月)
本月快讯 • 2023年2月25日,以"技术构筑万物智联"为主题的第一届开放原子开源基金会OpenAtom OpenHarmony(以下简称"OpenHarmony& ...
- 【直播回顾】参与ArkUI,共建OpenHarmony繁荣生态
5月31日晚上19点,战"码"先锋第三期直播 <参与ArkUI,共建OpenHarmony繁荣生态> ,在OpenHarmony社群内成功举行. 本期课程,由华为终 ...