惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
Y
Y Combinator Blog
雷峰网
雷峰网
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
博客园 - 聂微东
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
罗磊的独立博客
M
MIT News - Artificial intelligence
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
大猫的无限游戏
大猫的无限游戏
V
V2EX
I
InfoQ
The Cloudflare Blog
Cloudbric
Cloudbric
Stack Overflow Blog
Stack Overflow Blog
V
Vulnerabilities – Threatpost
博客园_首页
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
T
The Exploit Database - CXSecurity.com
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
美团技术团队
K
Kaspersky official blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
P
Privacy International News Feed
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cyberwarzone
Cyberwarzone
Vercel News
Vercel News
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
G
Google Developers Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
A
Arctic Wolf
MongoDB | Blog
MongoDB | Blog

博客园 - 野生西瓜

[Unity 杂货铺] SpriteAtlas 和 SBP 打包 [Unity 杂货铺] 游戏字体选择 [Unity 杂货铺] 游戏项目的结构规划与初始化 [Unity 杂货铺] Git 配置与实践 [Unity 杂货铺] 引擎版本的选择 [Unity] 基础寻路算法 - 环境搭建 [Unity] 基础寻路算法 - 代码实践 [Unity] 资源工作流程 - AssetPostprocessor [Unity] 资源工作流程 - ScriptedImporter [Unity] 资源工作流程 - 辅助工具 [Lua游戏AI开发指南] 笔记零 - 框架搭建 [GAME] [Civilization] 文明6字体及字体大小修改 [Unity] 编辑器运行中动态编译执行C#代码 [Unity] 在软件标题栏显示工作路径 [GAMEDEV] 个人开发如何找到合适的图片素材? [施工中] 博客导航 2021 的书 [BACKUP] Visual Studio Code 配置 [theHunterCOTW] 猎人荒野的召唤-一点资料
[Unity] 引擎脚本相关的字符串优化
野生西瓜 · 2022-04-23 · via 博客园 - 野生西瓜

本文始发于:https://www.cnblogs.com/wildmelon/p/16180980.html

一、参考资料

  1. .Net源代码,https://referencesource.microsoft.com/#mscorlib/system/string.cs
  2. 字符串和文本,https://docs.unity.cn/cn/current/Manual/BestPracticeUnderstandingPerformanceInUnity5.html
  3. Concatenating Strings Efficiently,https://jonskeet.uk/csharp/stringbuilder.html

二、序数比对

根据 官方文档优化建议 中提到的:

在与字符串相关的代码中经常出现的核心性能问题之一,是无意间使用了缓慢的默认字符串 API。这些 API 是为商业应用程序构建的,可根据与文本字符有关的多种不同区域性和语言规则来处理字符串。

这里提到的区域性,即指与 CultureInfo 有关的处理,在 string.cs 中,大约有以下方法与 CultureInfo 有关:

  1. Equals
  2. Compare
  3. StartsWith
  4. EndsWith
  5. ToUpper
  6. ToLower

除了 ToUpperToLower 之外,都是与字符串对比有关的。绝大多数情况下我们不需要认定字符'e'与'æ'之类的字符是相同的。所以需要改用带 StringComparison 形参的方法,切换到 StringComparison.Ordinal,直接按序数进行比对。否则可能产生数十到上百倍的性能差距:

// 耗时对比
public void TestString1() {
    System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
    int loopCount = 1000000;
    string strA = "test1";
    string strB = "test2";
    stopwatch.Start();
    for (int i = 0; i < loopCount; i++) {
        string.Compare(strA, strB);
        //strA.Equals(strB, StringComparison.CurrentCulture);
    }
    stopwatch.Stop();
    Debug.Log("Compare method 1: " + stopwatch.Elapsed.TotalMilliseconds);

    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 0; i < loopCount; i++) {
        string.CompareOrdinal(strA, strB);
        //strA.Equals(strB, StringComparison.Ordinal);
    }
    stopwatch.Stop();
    Debug.Log("Compare method 2: " + stopwatch.Elapsed.TotalMilliseconds);
}

// 某次不严谨的测试结果:
// Compare method 1: 574.2263
// Compare method 2: 7.5011

不过与 Unity文档讲的不同,Equals 接口似乎默认就是使用的 StringComparison.Ordinal 序数比对。

另外类似 a.StartsWith("b", System.StringComparison.Ordinal) 本身效率已经足够,似乎也没有必要自己重新实现一个 CustomStartsWith,有可能会忽略掉某些提前退出的情况,反而使得性能下降。

三、字符串拼接

(一)加号操作符

基本上在 StringBuilder 的相关教程上,都能看到类似如下的示例:

public string TestString2() {
    // 会产生多个临时对象,产生 GC
    string str = "";
    str = str + "a";
    str = str + "b";
    str = str + "c";
    return str;
}

使用 ILSpy 工具打开编译之后的 dll 文件,可见 C# 源码被编译为以下 IL 代码,可以看到 string 的加号操作符,实际上调用的是 string.Concat 方法:

.method public hidebysig 
    instance string TestString2 () cil managed 
{
    IL_0000: ldstr ""
    IL_0005: ldstr "a"
    IL_000a: call string [mscorlib]System.String::Concat(string, string)
    IL_000f: ldstr "b"
    IL_0014: call string [mscorlib]System.String::Concat(string, string)
    IL_0019: ldstr "c"
    IL_001e: call string [mscorlib]System.String::Concat(string, string)
    IL_0023: ret
} // end of metho

阅读 Concat 方法的源码,会发现底层是在统计所需长度后,调用 FastAllocateString 一次性分配内存,然后 FillStringChecked 对源字符串进行拷贝,实际上是一个非常效率的接口:

[System.Security.SecuritySafeCritical]  // auto-generated
private static String ConcatArray(String[] values, int totalLength) {
    String result =  FastAllocateString(totalLength);
    int currPos=0;

    for (int i=0; i<values.Length; i++) {
        Contract.Assert((currPos <= totalLength - values[i].Length), 
                        "[String.ConcatArray](currPos <= totalLength - values[i].Length)");

        FillStringChecked(result, currPos, values[i]);
        currPos+=values[i].Length;
    }

    return result;
}

(二)拼接优化

那么对于刚才的示例,是否有必要改成如下代码呢:

StringBuilder builder = new StringBuilder();
builder.Append ("a");
builder.Append ("b");
builder.Append ("c");
string result = builder.ToString();   

对于已知数量的字符串拼接,其实直接改成 string str = "a"+"b"+"c"; 即可。单行的连续加号运算会编译成 String Concat(params String[] values) 方法调用,既保持可读性又不会生成多个中间字符串。

严格来讲上述说明不太准确。string str = "a"+"b"+"c"; 生成的 IL 代码如下所示:

.method public hidebysig 
    instance string TestString2 () cil managed 
{
    .maxstack 8

    IL_0000: ldstr "abc"
    IL_0005: ret
} 

可见如果代码里用的是常量表达式,在编译时就能确认字符串内容的话,会进行优化直接跳过拼接的调用。但使用 StringBuilder 的话则是无法提前确认进行优化的。

(三)使用总结

StringBuilder 和 string.Concat 效率是相近的。

对于预先组织好的、固定数量的字符串拼接(能直接确认总长度),string.Concat 效率稍高一些,可读性也更高。

对于不确定数量的或者需要缓存中间结果的字符串拼接,则使用 StringBuilder。

顺带一提,String.Format 是效率更低的字符串接口,看似只用了一个额外字符串,实际上会逐字符遍历解析占位符。如果只是用作拼接目的,先考虑是否能用其他接口替代。

四、C# 0GC 字符串方案

StringBuilder 和 Concat,每次调用都会分配新的内存来保存新字符串,在项目中如果存在大量的字符串拼接,就会导致频繁的 GC Alloc。

可以考虑使用 https://github.com/871041532/zstring

参考以下文章说明:

  1. Unity中的string gc优化,https://www.cnblogs.com/zhaoqingqing/p/13928469.html
  2. ZString — Zero Allocation StringBuilder for .NET Core and Unity.