Unity 游戏的 string interning 优化
来源:互联网 发布:编程找质数 编辑:程序博客网 时间:2024/05/17 19:20
Unity 游戏的 string interning 优化
问题描述
在开始之前,先说一下这个问题为什么很容易被忽视吧。
正常情况下,我们通常难以注意到运行着的 Unity 程序内 string 的实例化情况。这些字符串的创建,销毁的时机是否合理,是否存在有重复 (相同内容的字符串),冗余 (存有已不再有意义的垃圾字符),低效 (capacity 远大于 length),以及泄漏 (没有在期望的时机及时销毁) 的情况就更容易被忽视了。由于 string 没法随时像普通的 Unity 对象那样通过调用 Object.GetInstanceID()
来查看实例id,我们不太容易感知字符串对象的实际内存开销。其实要不是偶然在工具里发现了大量的此类情况,俺也没想到看起来颇单纯的 immutable string 里居然隐藏着这么多秘密。
一次只说一件事,这次我们只讨论重复字符串的问题。
使用自制工具 ResourceTracker ,可以发现 Unity 游戏运行时 mono(il2cpp) 内有大量重复的字符串,如下所示:
手动 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 构造出来的,这样就解释得通了。( String literals get interned automatically )
手动 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); // TrueDebug.Log(foobar0 == foobar2); // TrueDebug.Log(foobar0 == foobar3); // TrueDebug.Log(foobar0 == foobar4); // TrueDebug.Log(System.Object.ReferenceEquals(foobar0, foobar1)); // FalseDebug.Log(System.Object.ReferenceEquals(foobar0, foobar2)); // TrueDebug.Log(System.Object.ReferenceEquals(foobar0, foobar3)); // FalseDebug.Log(System.Object.ReferenceEquals(foobar0, foobar4)); // True
注意,C# 并没有提供“清除已经 Intern 的字符串”的接口。也就是说,如果不由分说地把产生的字符串都扔进去,会造成大量短生命期字符串 (如某个地图上特有的特效名) 在全局池内的堆积。
解决这个问题并不难,手写一个可清除的版本就可以了。
可清除的 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 basis public static void Clear();}
通过参数 removable
我们可以指定使用默认 intern 还是 removable-intern。显式地指定后者的字符串将可被随后的 UniqueString.Clear()
清理。
UniqueString
的实现 (及更新) 在 这里 。
效果
使用上面的机制在关键点加了几行代码简单地优化后,内存中的字符串从 88000 条降低到 34000 条左右 (仍有很多重复存在)。
小结
- 直接写在代码里的常量字符串 (即所谓的 literal string) 会在启动时被系统自动 Intern 到系统字符串池;而通过拼接,解析,转换等方式在运行时动态产生的字符串则不会。
- 避免在 C# 代码里写多行的巨型 literal string,避免无谓的内存浪费。常见的情况是很大的 Lua 代码块,很密集的生成路径,大块 xml/json 等等,见下面的例子。
- 已经被自动或手动 Intern 的字符串在之后的整个生命期中常驻内存无法移除,但可以使用上面提供的
UniqueString
类实现周期性的清理。
下面是一些 不合理的 常见的代码内的常量字符串的情况 (都是常驻内存无法释放的)
string query = @"SELECT foo, bar FROM table WHERE id = 42";string lua_code_block = @" local ns = foo.bar(self.nID) for i,v in ipairs(self.imgs) do if (i - 1) < ns then Obj.SetActive(self.imgs[i], true) else Obj.SetActive(self.imgs[i], false) end end";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 细节非常有帮助
- 本文遵循 Creative Commons BY-NC-ND 4.0 许可协议
[完]
Gu Lu
[2016-11-22]
原文:http://www.gulu-dev.com/post/perf_assist/2016-11-22-unity-string-intern?utm_source=tuicool&utm_medium=referral
【关于我们】
每天名企社招内推(微信公众号:ourpush),专注于国内各大互联网公司社会招聘内推。每天更新最新互联网名企(包括但不限于网易游戏、BAT、网易互联网、小米、京东、乐视、携程等名企)内推信息,有技术岗、有产品岗、有运营岗、有设计岗、有交互岗、有销售岗,更有其他N多相关岗位!更多内推信息请扫描以下二维码关注查阅。
- Unity 游戏的 string interning 优化
- Unity 游戏的String interning优化
- 字符串的驻留(String Interning)
- 字符串的驻留(String Interning)
- 字符串的驻留(String Interning)
- 字符串的驻留(String Interning)
- 字符串的驻留(String Interning)
- 字符串的驻留(String Interning)
- String Interning研究
- unity - 优化你的游戏
- .Net Framework: 字符串的驻留(String Interning)
- [原创].Net Framework: 字符串的驻留(String Interning)
- String interning 和 String.Empty
- Unity开发IOS游戏的优化建议
- 全新的Unity移动游戏优化解决方案
- Unity开发IOS游戏的优化建议
- Unity游戏的GC(garbage collection)优化
- unity游戏优化
- 周星驰的那句话
- 彻底明白Activity启动模式-SingleTop、SingleTask、SingleInstance具体使用场景
- 可重入函数
- 无线传感网初识及其结构示意图
- ARC下的内存泄漏
- Unity 游戏的 string interning 优化
- win10+64位 安装Theano并实现GPU加速
- 破解网页版 百度云网盘无法下载大文件限制
- 理解RESTful架构
- Linked List Cycle ---LeetCode
- C++并发编程
- 数据类型-变量与常量
- 坚持#第104天~怎么能怕死呢,怕死的人死得早,不怕死的人死不了,这个时候不努力什么时候努力!
- weak 和 assign 的不同点