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 TextMeshProtmp.SetText(sb);// SetText(Utf16ValueStringBuilder) is the same as followingvar 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.mzph.cn/news/707365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新版极狐gitlab安装+配置详细版

这里安装的服务器环境是centos7.9系统&#xff0c;安装极狐版本16.9。 极狐地址&#xff1a;https://gitlab.cn/install/ 1. 安装和配置所需的依赖 在 CentOS 7 上&#xff0c;下面的命令会在系统防火墙中打开 HTTP、HTTPS 和 SSH 访问。这是一个可选步骤&#xff0c;如果您…

Docker部署Portainer图形化管理工具

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 Portainer 是一个轻量级的容器管理工具&#xff0c;可以通过 Web 界面对 Docker 容器进行管理和监控。它提供了可…

物业智能水电抄表管理系统

物业智能水电抄表管理系统是物业管理行业的关键技术之一&#xff0c;其结合了智能化、远程监控和数据分析等功能&#xff0c;为物业管理公司和业主提供了高效、精准的水电抄表管理解决方案。该系统具有多项优势&#xff0c;能够提升物业管理效率&#xff0c;降低成本&#xff0…

第五节:Vben Admin权限-前端控制方式

系列文章目录 第一节:Vben Admin介绍和初次运行 第二节:Vben Admin 登录逻辑梳理和对接后端准备 第三节:Vben Admin登录对接后端login接口 第四节:Vben Admin登录对接后端getUserInfo接口 第五节:Vben Admin权限-前端控制方式 文章目录 系列文章目录前言一、Vben Admin权…

py32 link,让PY32单片机开发更容易上手。

py32 link支持PY32系列单片机的调试和烧录&#xff0c;⽀持Keil、IAR等多种开发环境&#xff0c;开发简单易上手。PY32 link使用Type-C接⼝供电&#xff0c;搭载了MH32F103A芯片 LQFP64封装&#xff0c;MH32F103A有着216MHz主频和256KB flash&#xff0c;96KB RAM大资源&#x…

【Python】Code2flow学习笔记

1 Code2flow介绍 Code2flow是一个代码可视化工具库&#xff0c;旨在帮助开发人员更好地理解和分析代码&#xff1a; 可以将Python代码转换为流程图&#xff0c;以直观的方式展示代码的执行流程和逻辑结构。具有简单易用、高度可定制化和美观的特点&#xff0c;适用于各种代码…

Groovy(第九节) Groovy 之单元测试

JUnit 利用 Java 对 Song 类进行单元测试 默认情况下 Groovy 编译的类属性是私有的,所以不能直接在 Java 中访问它们,必须像下面这样使用 setter: 编写这个测试用例余下的代码就是小菜一碟了。测试用例很好地演示了这样一点:用 Groovy 所做的一切都可以轻易地在 Java 程序…

算法--动态规划(线性DP、区间DP)

这里写目录标题 tip数组下标从0开始还是从1开始 线性DP数学三角形介绍算法思想例题代码 最长上升子序列介绍算法思想例题代码 最长公共子序列介绍算法思想例题代码 编辑距离介绍例题代码 区间DP问题石子合并介绍算法思想例题代码 tip 数组下标从0开始还是从1开始 如果代码中涉…

Opencv实战(3)详解霍夫变换

霍夫变换 Opencv实战系列指路前文&#xff1a; Opencv(1)读取与图像操作 Opencv(2)绘图与图像操作 文章目录 霍夫变换1.霍夫线变换1.1 原理1.2 HoughLines() 2.霍夫圆变换2.1 原理2.2 HoughCircles() 最基本的霍夫变换是从黑白图像中检测直线(线段) 霍夫变换(Hough Transform…

【vue】什么是虚拟Dom,怎么实现虚拟DOM,虚拟DOM一定更快吗

什么是虚拟Dom 虚拟 DOM 基于虚拟节点 VNode&#xff0c;VNode 本质上是一个对象&#xff0c;VDOM 就是VNode 组成的 废话&#xff0c;js 中所有的东西都是对象 虚拟DOM 为什么快&#xff0c;做了哪些优化 批量更新 多个DOM合并更新减少浏览器的重排和重绘局部更新 通过新VDO…

Spring中的ApplicationContext.publishEvent

简单理解 其实就是监听处理。比如找工作平台上&#xff0c;雇主 employer 发布自己的雇佣条件&#xff0c;目的是平台中有符合条件的求职者时&#xff0c;及时向雇主推荐。求职者发布简历&#xff0c;当平台发现某个求职者比较符合条件&#xff0c;就触发被动&#xff0c;推荐…

selenium元素等待及滚动条滚动

selenium三大等待&#xff0c;sleep&#xff08;强制&#xff09;、implicitlyWait&#xff08;隐式等待&#xff09;、WebDriverWait&#xff08;显式等待&#xff09;&#xff0c;主要记一下最后面的WebDriverWait。 WebDriverWait是三大等待中最常用也是最好用的一种等待方…

docker 容器修改端口和目录映射

一、容器修改端口映射 一般在运行容器时&#xff0c;我们都会通过参数 -p&#xff08;使用大写的-P参数则会随机选择宿主机的一个端口进行映射&#xff09;来指定宿主机和容器端口的映射&#xff0c;例如 docker run -it -d --name [container-name] -p 8088:80 [image-name]…

vue3的echarts从后端获取数据,用于绘制图表

场景需求&#xff1a;后端采用flask通过pymysql从数据库获取数据&#xff0c;并返回给前端。前端vue3利用axios获取数据并运用到echarts绘制图表。 第一步&#xff0c;vue中引入echarts 首先vue下载echarts npm install echarts 然后在main.js文件写如下代码 import {create…

东芝工控机维修东芝电脑PC机维修FA3100A

TOSHIBA东芝工控机维修电脑控制器PC机FA3100A MODEL8000 UF8A11M 日本东芝TOSHIBA IA controller维修SYU7209A 001 FXMC12/FXMC11;BV86R-T2GKR-DR7YF-8CPPY-4T3QD; CPU处理单元是可编程逻辑控制器的控制部分。它按照可编程逻辑控制器系统程序赋予的功能接收并存储从编程器键入…

基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面

目录 简介注册与软件获取工程配置设计 UI导出源码板级验证更多内容 简介 SquareLine Studio 是一款专业的 UI 设计软件&#xff0c;它与 LVGL&#xff08;Light and Versatile Graphics Library&#xff0c;轻量级通用图形库&#xff09;紧密集成。LVGL 是一个轻量化的、开源的…

K8S之Deployment的介绍和使用

Deployment的理论和实操 Deployment控制器&#xff1a;概念、原理解读概述工作原理 编写Deployment资源清单文件使用案例&#xff1a;创建一个web站点Deployment管理pod&#xff1a;扩容、缩容通过deployment管理应用&#xff0c;实现扩容&#xff0c;把副本数变成3通过deploym…

135 Linux 系统编程12,linux命令重定向,dup 和dup2,fcntl实现dup和dup2 ,进程和程序概念,虚拟内存和物理内存映射关系,pcb进程块详解

一 linux 命令中重定向&#xff0c;使用>实现 通过 大于号 将前面的内容写入到某一个地方 cat main.c > b.txt //cat main 本身的意思是 显示main.c的值&#xff0c;后面加上 > b.txt 会将显示在屏幕上的字符全部写到b.txt中 例子&#xff1a;将 ls -l 的内容 通…

JavaScript最新实现城市级联操作,json格式的数据

前置知识&#xff1a; <button onclick"doSelect()">操作下拉列表</button><hr>学历&#xff1a;<select id"degree"><option value"0">--请选择学历--</option><option value"1">专科<…

配置前端项目到 github-pages

Quickstart for GitHub Pages - GitHub Docs