Unity中字符串拼接0GC方案

本文主要分析C#字符串拼接产生GC的原因,以及介绍名为ZString的库,它可以将字符串生成的内存分配为零。

在C#中,字符串拼接通常有三种方式:

  1. 直接使用+号连接;
  2. string.format;
  3. 使用StringBuilder;

下面分别细述。

故事的开始

首先,简单介绍下String类型。C# String 类型内部是“UTF-16”字节字符串。

与普通对象一样,它有一个对象头,并在堆内存中分配。同样,字符串基本上只能由“新字符串”生成。'StringBuilder.ToString','Encoding.GetString'等,最后也调用'new string'来分配一个新字符串。

即使是相同的字符串值,“new string”生成的字符串也会分配在不同的内存空间中。只有常量字符串从称为实习生池的应用程序共享空间获取固定引用。

var x = new string(new[] { 'f', 'o', 'o' });
var y = new string(new[] { 'f', 'o', 'o' });
var z = "foo";
var u = "foo";
var v = String.Intern(x);
 
// different reference: x != y != z
Console.WriteLine(Object.ReferenceEquals(x, y)); // false
Console.WriteLine(Object.ReferenceEquals(x, z)); // false
 
// same reference: z == u == v
Console.WriteLine(Object.ReferenceEquals(z, u)); // true
Console.WriteLine(Object.ReferenceEquals(z, v)); // true
 
// same value
Console.WriteLine(x == y && x == z && x == u && x == v); // true

如果你想从intern池中获取,可以使用'String.Intern'方法。Intern方法是从Intern池中获取的。如果不存在,则注册并返回其引用。由于Intern池中注册的内存无法删除,因此可能很难很好地使用它。

+拼接(String.Concat)

使用+号连接时,C# 编译器会进行专门处理,将其转换为 String.Concat。

string.Concat(object arg0, object arg1)
string.Concat(object arg0, object arg1, object arg2)
string.Concat(params object[] values)
string.Concat(string str0, string str1)
string.Concat(string str0, string str1, string str2)
string.Concat(string str0, string str1, string str2, string str3)
string.Concat(params string[] values)

不同的编译器版本处理稍有不同。例如,Visual Studio 2019 的 C# 编译器 (int x) + (string y) + (int z) 的结果将为“String.Concat(x.ToString(), y, z.ToString())”。但是,Visual Studio 2017 的 C# 编译器将是“String.Concat((object)x, y, (object)z)”,如果连接非字符串参数,将使用对象重载。因此,发生了结构装箱。

如果我们连接的字符不匹配上方的重载,比如,连接了5个字符,那么就会产生一个“params array”的分配,同样会造成额外的GC。

针对上述情况,ZString提供了最多15个参数的泛型重载,且在内部使用了“Utf16ValueStringBuilder”(在StringBuilder小节中有解释),因此几乎可以完全避免数字类型的字符串转换分配。

StringBuilder

“StringBuilder”是一个以“char[]”作为临时缓冲区的类。StringBuilder.Append()方法用于写入缓冲区,StringBuilder.ToString() 生成最终字符串。

public class SimpleStringBuilder
{
    char[] buffer;
    int offset;
 
    public void Append(string value)
    {
        value.CopyTo(0, buffer, offset, value.Length);
        offset += value.Length;
    }
 
    public override string ToString()
    {
        return new string(buffer, 0, offset);
    }
}

如果要连接多个字符串,应避免使用“+=”,因为每个“+=”都会生成一个新字符串。StringBuilder 避免生成这个临时的新字符串,而是将其复制到“char[]”。

当追加数字以及某些类型时,.NET Standard 2.0(Unity 等)和 .NET Standard 2.1(.NET Core 3.0 等)之间的行为会有所不同。

// .NET Standard 2.0
public StringBuilder Append(int value)
{
    return Append(value.ToString(CultureInfo.CurrentCulture));
}
 
// .NET Standard 2.1
public StringBuilder Append(int value)
{
    return AppendSpanFormattable(value);
}
 
private StringBuilder AppendSpanFormattable<T>(T value)
    where T : ISpanFormattable
{
    if (value.TryFormat(RemainingCurrentChunk,
        out int charsWritten, format: default, provider: null))
    {
        m_ChunkLength += charsWritten;
        return this;
    }
    return Append(value.ToString());
}

对于 .NET Standard 2.0,它Append时调用了ToString方法。但在 .NET Standard 2.1 中,“ISpanFormattable.TryFormat”将其直接写入缓冲区,而不通过字符串。ISpanFormattable这个接口是internal的 。但是,通过检查 [ ISpanFormattable.references ],您可以看到哪种类型实现了此接口。

通过ZString可以避免添加数字类型时的字符串分配。在 .NET Standard 2.1 中,ZString 使用它们的TryFormat。在.NET Standard 2.0中,ZString使用移植的TryFormat方法。

API 本身与 StringBuilder 几乎相同。但是,它必须用“using”括起来。

// using ZString.CreateStringBuilder instead of new StringBuilder
using (var sb = ZString.CreateStringBuilder())
{
    sb.Append(enemy.Name);
    sb.Append(" Current HP:");
    sb.Append(enemy.Hp);
    sb.Append(" Current MP:");
    sb.Append(enemy.Mp);
    if (addStatus)
    {
        sb.Append(" Status:");
        sb.Append(enemy.Status);
    }
    return sb.ToString();
}

ZString.CreateStringBuilder ()方法的返回值“Utf16ValueStringBuilder”是一个结构体,所以避免了分配到StringBuilder的堆内存。此外,由于用于内部写入的“char[]”缓冲区是从ArrayPool获取的,因此避免了缓冲区分配。(这也是为什么需要通过“using”返回缓冲区。)

String.Format

由于 String.Format 的参数只能接受对象,因此会发生装箱。

// conversion of String interpolation is rewrited to following by C# compiler
$"{enemy.Name} Current Hp:{enemy.Hp} Current Mp:{enemy.Mp}";
 
// string.Object(string, object, object, object)
String.Format("{0} Current Hp:{1} Current Mp:{2}", enemy.Name, enemy.Hp, enemy.Mp);
 
// String.Format can avoid params array until 3 arguments
string string.Format(string format, object arg0)
string string.Format(string format, object arg0, object arg1)
string string.Format(string format, object arg0, object arg1, object arg2)
string string.Format(string format, params object[] args)

此外,与 StringBuilder.Append 一样,在 .NET Standard 2.0 中,也会发生字符串转换分配。

与“ZString.Concat”一样,“ZString.Format”具有最多 15 个参数的通用重载。即使在.NET Standard 2.0环境下,通过TryFormat直接转换,也能实现零分配。

终极秘诀

ZString 的内部实现是零分配。但当最后总要输出一个字符串,还是会产生GC。但是,如果适用的库具有接受字符串以外的内容的 API,则也可以避免最终的字符串生成,并且可以实现完全零分配。例如,TextMeshPro有一个名为“SetCharArray(char[] sourceText, int start, int length)”的API,可以直接给出它,并且可以避免字符串生成。

TMP_Text tmp;
 
// create StringBuilder
using(var sb = ZString.CreateStringBuilder())
{
    sb.Append("foo");
    sb.AppendLine(42);
    sb.AppendFormat("{0} {1:.###}", "bar", 123.456789);
 
    // direct write(avoid string alloc) to TextMeshPro
    tmp.SetText(sb);
 
    // SetText(Utf16ValueStringBuilder) is the same as following
    var buffer= sb.AsArraySegment();
    tmp.SetCharArray(buffer.Array, buffer.Offset, buffer.Count);
}
 
// convinient helper to use ZString.Format
tmp.SetTextFormat("Position: {0}, {1}, {2}", x, y, z);
 
// other ZString direct write utilities
.AsSpan()
.AsMemory()
.TryCopyTo(Span<char>, out int writtenChars);

参考文献:

ZString — Zero Allocation StringBuilder for .NET Core and Unity.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/414413.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

express+mysql+vue,从零搭建一个商城管理系统4--mysql数据库链接

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、创建express_service数据库二、安装mysql三、新建config文件夹四、新建config/db.js五、index.js引入db.js文件六、启动项目预览总结 前言 需求&#xff1a;主要学习express&#xff0c;所以先写service…

高级语言期末2011级A卷(软件学院)

1.编写函数&#xff0c;判定正整数m和n&#xff08;均至少为2&#xff09;是否满足&#xff1a;数m为数n可分解的最小质因数&#xff08;数n可分解的最小质因数为整除n的最小质数&#xff09; 提示&#xff1a;判定m为质数且m是n的最小因数 #include <stdio.h> #include…

导览系统厂家|景区电子导览|手绘地图|AR导览|语音导览系统

随着元宇宙、VR、AR等新技术的快速发展&#xff0c;旅游服务也更加多元化、智能化。景区导览系统作为旅游服务的重要组成部分&#xff0c;其形式更加多元化智能化。智能导览系统作为一种新的服务方式&#xff0c;能够为游客提供更加便捷的旅游服务和游览体验&#xff0c;也逐渐…

【经验】vscode 鼠标拖曳不能选中整行文字,只能选中纵向矩形范围

1、问题描述 不知道昨天操作vscode设置界面时&#xff0c;误选择了啥&#xff0c;导致鼠标拖曳不能选中整行文字&#xff0c;只能选中纵向矩形范围&#xff0c;现象如下&#xff1a; 2、解决方法 1&#xff09;打开设置界面 点击左下角按键&#xff0c;选择“设置” 2&…

在CentOS上使用Docker搭建Halo博客并实现远程访问的详细指南

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. Docker部署Halo1.1 检查Docker版本1.2 在Docker中部署Halo 二. Linux安装Cpol…

Oracle中序列

1. Sequence 定义 在Oracle中可以用SEQUENCE生成自增字段。Sequence序列是Oracle中用于生成数字序列的对象&#xff0c;可以创建一个唯一的数字作为主键。 2. 为什么要用 Sequence 你可能有疑问为什么要使用序列&#xff1f; 不能使用一个存储主键的表并每次递增吗&#xf…

第102讲:MySQL多实例与Mycat分布式读写分离的架构实践

文章目录 1.Mycat读写分离分布式架构规划2.在两台服务器中搭建八个MySQL实例2.1.安装MySQL软件2.2.创建每个MySQL实例的数据目录并初始化2.3.准备每个实例的配置文件2.4.准备每个实例的启动脚本2.6启动每台机器的MySQL多实例2.7.为每个MySQL实例设置密码2.8.查看每个MySQL实例的…

【寸铁的刷题笔记】图论、bfs、dfs

【寸铁的刷题笔记】图论、bfs、dfs 大家好 我是寸铁&#x1f44a; 金三银四&#xff0c;图论基础结合bfs、dfs是必考的知识点✨ 快跟着寸铁刷起来&#xff01;面试顺利上岸&#x1f44b; 喜欢的小伙伴可以点点关注 &#x1f49d; &#x1f31e;详见如下专栏&#x1f31e; &…

微信小程序 获取openId - 成功获取

1.点击云开发配置里面的信息 2-基础信息配置结束之后 - 新建一个云函数 - 名字自定义但是要记住&#xff0c;这里定义为login3.保存成功之后出现这个 代表成功 - 这个弹窗可以关闭了 4.定义一个方法 name:login, 这里需要修改成对应的 是刚才上面定义的云函数名称 info()…

博途PLC 单通气缸功能块(SCL源代码)

气缸是工业现场应用非常多的一个重要执行器,气缸在很多场合都有大量应用,今天我们的对象就是"单通气缸",不同的工程师,不同的应用行业,大家对气缸功能块的封装会有所不同。气缸功能块的其它封装大家可以参看下面文章 1、气缸功能块 https://rxxw-control.blog…

mac打不开xxx软件, 因为apple 无法检查其是否包含恶意

1. 安全性与隐私下面的允许来源列表&#xff0c;有些版本中的‘任何来源’选项被隐藏了&#xff0c;有些从浏览器下载的软件需要勾选这个选项才能安装 打开‘任何来源’选项 sudo spctl --master-disable 关闭‘任何来源’选项 sudo spctl --master-enable

DVWA 靶场之 Command Injection(命令执行)原理介绍、分隔符测试、后门写入与源码分析、修复建议

在打靶之前我们需要先解决一个乱码问题 参考我之前的博客&#xff1a; 关于DVWA靶场Command Injection&#xff08;命令注入&#xff09;乱码的解决方案-CSDN博客 简单介绍一下命令执行漏洞&#xff1a; 命令执行漏洞是一种常见的网络安全漏洞&#xff0c;它允许攻击者通过向应…

微服务-商城订单服务项目

文章目录 一、需求二、分析三、设计四、编码4.1 商品服务4.2 订单服务4.3 分布式事务4.4 订单超时 商品、购物车 商品服务&#xff1a; 1.全品类购物平台 SPU:Standard Product Unit 标准化产品单元。是商品信息聚合的最小单位。是一组可复用、易检索的标准化信息的集合&#x…

Biotin-PEG2-Thiol,生物素-PEG2-巯基,应用于抗体标记、蛋白质富集等领域

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Biotin-PEG2-Thiol&#xff0c;生物素-PEG2-巯基&#xff0c;Biotin PEG2 Thiol&#xff0c;生物素 PEG2 巯基 一、基本信息 【产品简介】&#xff1a;Biotin PEG2 Thiol can bind with antibodies to prepare biot…

第12课-在网络上写作,让你事半功倍的14个独家秘诀

公众号文章写作方向要专一&#xff0c;太杂的内容很难留住用户&#xff0c;这就要求我们早起尽快确定自己擅长的领域&#xff0c;然后不断精进。 在写作的过程中确定自己的写作风格&#xff0c;而写作风格的确立需要长时间的写作积累&#xff0c;会经历迷茫和混乱&#xff0c;…

数据库管理-第156期 Oracle Vector DB AI-07(20240227)

数据库管理156期 2024-02-27 数据库管理-第156期 Oracle Vector DB & AI-07&#xff08;20240227&#xff09;1 Vector相关DDL操作可以在现有的表上新增vector数据类型的字段&#xff1a;可以删除包含vector数据类型的列&#xff1a;可以使用CTAS的方式&#xff0c;从其他有…

centos7系统crond 离线安装

CentOS 7的crond离线安装步骤如下&#xff1a; 下载cronie和crontabs的离线安装包。可以从rpmfind等网站下载与CentOS 7系统版本相匹配的cronie和crontabs离线安装包。 将下载的离线安装包传输到CentOS 7服务器上。 依次执行以下命令进行安装&#xff1a; 安装cronie&#…

【喜讯】优积科技获上海市室内装饰行业“装配式装修示范单位”

2023年12月29日下午&#xff0c;上海市室内装饰行业协会第九届第三次会员大会暨第五次理事会在金茂大厦隆重举行。上海市民政局社会团体管理处副处长姜琦、上海市经济和信息化委员会综合规划处副处长赵广君、上海市工经联党委副书记、执行副会长黄国伟等领导应邀出席会议。出席…

CSS3技巧37:JS+CSS3 制作旋转图片墙

开学了就好忙啊&#xff0c;Three.js 学习的进度很慢。。。 备课备课才是王道。 更一篇 JS CSS3 的内容&#xff0c;做一个图片墙。 其核心要点是把图片摆成这个样子&#xff1a; 看上去这个布局很复杂&#xff0c;其实很简单。其思路是&#xff1a; 所有图片放在一个 div.…

kubectl 声明式资源管理方式

目录 介绍 YAML 语法格式 命令 应用yaml文件指定的资源 删除yaml文件指定的资源 查看资源的yaml格式信息 查看yaml文件字段说明 查看 api 资源版本标签 修改yaml文件指定的资源 离线修改 在线修改 编写yaml文件 创建资源对象 查看创建的pod资源 创建service服务对…