Unity 游戏的String interning优化
原文链接:http://blog.uwa4d.com/archives/USparkle_String-interning.html
我们通常难以注意到运行着的Unity引擎内String的实例化情况。比如这些字符串创建、销毁的时机是否合理,是否存在有重复、冗余、低效以及泄漏的情况。如何解决这个问题?也许笔者的一番研究和结论正能解决大家的痛点。
这是侑虎科技第155篇原创文章,感谢作者顾露供稿。欢迎转发分享,未经作者授权请勿转载。作者博客:http://gulu-dev.com。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)
同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U Sparkle开发者计划,这个舞台有你更精彩!
◆◆◆
问题描述
在此之前,先说一下这个问题为什么很容易被忽视吧。
正常情况下,我们通常难以注意到运行着的Unity引擎内String的实例化情况。比如这些字符串创建、销毁的时机是否合理,是否存在有重复 (相同内容的字符串)、冗余 (存有已不再有意义的垃圾字符)、低效 (capacity远大于length),以及泄漏 (没有在期望的时机及时销毁) 的情况。由于String无法随时像普通的Unity对象那样通过调用 Object.GetInstanceID() 来查看实例ID,我们不太容易感知字符串对象的实际内存开销。若非偶然在工具里发现了大量的此类情况,笔者也没想到看起来颇单纯的immutable string里居然隐藏着这么多秘密。
一次只说一件事,这次我们只讨论重复字符串的问题。
◆◆◆
优化步骤
使用自制工具ResourceTracker,可以发现Unity项目运行时 mono(il2cpp) 内有大量重复的字符串,如下所示:
1. 手动 Intern()
对 .Net 特性有了解的同学,应该知道C#同Java一样,提供了一套内建的String interning机制,能够在后台维护一个字符串池,从而保证让同样内容的字符串始终复用同一个对象。这么做有两个好处,一个是节省了内存 (重复字符串越多,内存节省量越大),另一个好处是降低了字符串比较的开销 (如果两个字符串引用一致,就不用逐字符比较内容了)。
但是为什么上面的Unity引擎内仍然有大量的重复字符串呢?
查看他们的地址,发现彼此各不相同,说明的确没有引用到同一块内存区域。由于C# 语言实现以静态的特性为主,笔者推测,也许只有编译期可以捕捉到的字符串 (也就是通常用字面字符串literal string来构建时) 才会interning。
做个实验吧
string foobar = "foobar";
string foobar2 = new StringBuilder().Append("foo").Append("bar").ToString();Debug.Log(foobar == foobar2);
Debug.Log(System.Object.ReferenceEquals(foobar, foobar2));
运行上面的代码,输出结果分别是 True 和 False。也就是说,即使运行时内容一样 (== 返回True),手动在运行时拼出来的字符串也不会自动复用已有的对象。查看游戏代码,发现很多重复字符串是通过解析binary stream或text stream构造出来的,这样就解释得通了。
手动 Intern 一下试试吧
string foobar0 = "foobar";
string foobar1 = new StringBuilder().Append("foo").Append("bar").ToString();
string foobar2 = string.Intern(foobar1);
string foobar3 = new StringBuilder().Append("f").Append("oo").Append("b").Append("ar").ToString();
string foobar4 = string.Intern(foobar3);Debug.Log(foobar0 == foobar1); // True
Debug.Log(foobar0 == foobar2); // True
Debug.Log(foobar0 == foobar3); // True
Debug.Log(foobar0 == foobar4); // True
Debug.Log(System.Object.ReferenceEquals(foobar0, foobar1)); // False
Debug.Log(System.Object.ReferenceEquals(foobar0, foobar2)); // True
Debug.Log(System.Object.ReferenceEquals(foobar0, foobar3)); // False
Debug.Log(System.Object.ReferenceEquals(foobar0, foobar4)); // True
注意,C# 并没有提供“清除已经Intern 的字符串”的接口。也就是说,如果不由分说地把产生的字符串都扔进去,会造成大量短生命期字符串(如某个地图上特有的特效名)在全局池内的堆积。解决这个问题并不难,手写一个可清除的版本就可以了。
2. 可清除的 Interning - UniqueString
下面的UniqueString 类除了提供两个与string.Intern() 和string.IsInterned() 一致的接口外,还提供了Clear() 接口用于周期性地释放整个字符串池,可在地图切换等时机调用。这个类通过判断参数来确认,是将字符串放入全局的系统池,还是支持周期性清理的用户池。
public class UniqueString
{// 'removable = false' means the string would be added to the global string pool// which would stay in memory in the rest of the whole execution period.public static string Intern(string str, bool removable = true) // Why return a ref rather than a bool? // return-val is the ref to the unique interned one, which should be tested against `null`public static string IsInterned(string str) // should be called on a regular basispublic static void Clear();
}
通过参数removable我们可以指定使用默认intern还是removable-intern。显式地指定后者的字符串将可被随后的 UniqueString.Clear() 清理。
UniqueString 的实现及更新可以参考:https://link.zhihu.com/?target=https%3A//github.com/PerfAssist/PA_Common/blob/master/UniqueString.cs
◆◆◆
效果和小结
使用上面的机制在关键点加了几行代码简单地优化后,内存中的字符串从88000条降低到 34000 条左右 (仍有很多重复存在)。
通过上述的试验测试,笔者得到的结论如下:
直接写在代码里的常量字符串 (即所谓的literal string) 会在启动时被系统自动Intern到系统字符串池;而通过拼接、解析、转换等方式在运行时动态产生的字符串则不会。
避免在C# 代码里写多行的巨型literal string,避免无谓的内存浪费。常见的情况是很大的Lua 代码块,很密集的生成路径,大块 xml/json 等等,大家可以参考下面的例子。
已经被自动或手动 Intern 的字符串在之后的整个生命期中常驻内存无法移除,但可以使用上面提供的 UniqueString 类实现周期性的清理。
下面是一些不合理的常见代码内的常量字符串的情况 (都是常驻内存无法释放的)
string query = @"SELECT foo, barFROM tableWHERE id = 42";string lua_code_block = @"local ns = foo.bar(self.nID)for i,v in ipairs(self.imgs) doif (i - 1) < ns thenObj.SetActive(self.imgs[i], true)elseObj.SetActive(self.imgs[i], false)endend
";string[] resFiles = new string[] { "Assets/Scenes/scene_01.unity", "Assets/Scenes/scene_02.unity", "Assets/Scenes/scene_03.unity", "Assets/Scenes/scene_04.unity", "Assets/Scenes/scene_05.unity"
};
附:
Understanding C#: String.Intern makes strings interesting 是很好的材料,对理解清楚intern的一些 dirty 细节非常有帮助。
相关参考:https://link.zhihu.com/?target=http%3A//broadcast.oreilly.com/2010/08/understanding-c-stringintern-m.html
本文遵循 Creative Commons BY-NC-ND 4.0 许可协议。
文末,再次感谢顾露的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!
Unity 游戏的String interning优化相关推荐
- Unity游戏排行榜的制作与优化
前言 游戏排行榜是一个很重要的功能,在弱联网的单机游戏与网络游戏中排行榜都是非常重要的,今天我们来详细的讲解游戏排行榜的制作方案,主要有4个点: 对啦!这里有个游戏开发交流小组 里面聚集了一帮热爱学习 ...
- Unity常用模块设计 : Unity游戏排行榜的制作与优化
游戏排行榜是一个很重要的功能,在弱联网的单机游戏与网络游戏中排行榜都是非常重要的,今天我们来详细的讲解游戏排行榜的制作方案,主要有4个点: 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交 ...
- Unity游戏优化指南大全(持续更新中!)
Unity游戏优化指南大全 三个官方优化提示: 性能和优化 (Performance and Optimization) - 关于性能分析器以及性能和优化技巧的 Unity 学习教程. Best pr ...
- Unity游戏优化(第2版)学习记录8
Unity游戏优化[第二版]学习记录8 第8章 掌握内存管理 一.Mono平台 1.垃圾回收 2.内存碎片 3.运行时的垃圾回收 4.多线程的垃圾回收 二.代码编译 三.分析内存 1.分析内存消耗 2 ...
- Unity游戏优化[第二版]学习记录6
以下内容是根据Unity 2020.1.01f版本进行编写的 Unity游戏优化[第二版]学习记录6 第6章 动态图形 一.管线渲染 1.GPU前端 2.GPU后端 3.光照和阴影 4.多线程渲染 5 ...
- Unity手机游戏发热发烫优化指南与技巧
Unity手机游戏发热发烫优化指南与技巧 很多小伙伴做完游戏后,发布到Android,运行,游戏很流畅,也不卡顿,但是跑一会游戏,手机就发热,发烫.客户提出需求,能否让它不发烫? 本文从以下3方面来分 ...
- 《Unity游戏优化》第三章 批处理
动态批处理.静态批处理.GPU Instancin 和SRP Batcher 批处理原理 批处理设置 Frame Debugger 1. 动态批处理 动态批处理使用场景 动态批处理优势: 动态批处理缺 ...
- Unity游戏优化[第二版]学习记录4
Unity游戏优化[第二版]学习记录4 第4章 着手处理艺术资源 一.音频 1.导入音频文件 2.加载音频文件 3.编码格式与品质级别 4. 音频性能增强 二.纹理文件 1.纹理压缩格式 2.纹理性能 ...
- 【游戏优化】AOI算法、Unity游戏优化(一)
Unity游戏优化.内存优化.资源优化.AOI算法.安全 AOI概念和设计 地图广播(地图消息同步) AOI解决的问题 AOI的设计 场景分析与方案设计(一) 改善方案 场景分析与方案设计(二) 场景 ...
最新文章
- android webview mailto,Webview email link (mailto)
- 买卖股票 状态机模型的理解
- destoon php,DESTOON_7.0_UTF8
- Maven+struts2+spring4+hibernate4的环境搭建
- MongoDB 常用故障排查工具
- filestream 生成xml 文件时被如何让禁止转义_从Edgecam到PCDMIS,如何将工艺工程师的思想加入质量检测?...
- 微软系统修复工具(试用版)
- SQLAlchemy 增删改查
- java服务端异步处理机制_Java异步处理机制实例详解
- python中绝对值怎么表示_python如何使用绝对值
- 什么是敏捷开发(Scrum)?
- android 倒水动画,Android 模拟圆形水杯倒水的效果
- granger Z-score问题
- 有穷自动机的最小化c语言代码,无符号数有穷自动机实现.doc
- PHP银联在线支付接口开发日志
- 图形化开发(一)——Three.js基本介绍-优缺点-在线编辑器 Babylon.JS是最好的JavaScript3D游戏引擎
- Linux下编写贪吃蛇游戏
- 2022危险化学品经营单位安全管理人员考试题库及在线模拟考试
- 用C语言编写一个无限循环语句
- 中软实习培训记录二(0721)