原文链接: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 条左右 (仍有很多重复存在)。

通过上述的试验测试,笔者得到的结论如下:

  1. 直接写在代码里的常量字符串 (即所谓的literal string) 会在启动时被系统自动Intern到系统字符串池;而通过拼接、解析、转换等方式在运行时动态产生的字符串则不会。

  2. 避免在C# 代码里写多行的巨型literal string,避免无谓的内存浪费。常见的情况是很大的Lua 代码块,很密集的生成路径,大块 xml/json 等等,大家可以参考下面的例子。

  3. 已经被自动或手动 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优化相关推荐

  1. Unity游戏排行榜的制作与优化

    前言 游戏排行榜是一个很重要的功能,在弱联网的单机游戏与网络游戏中排行榜都是非常重要的,今天我们来详细的讲解游戏排行榜的制作方案,主要有4个点: 对啦!这里有个游戏开发交流小组 里面聚集了一帮热爱学习 ...

  2. Unity常用模块设计 : Unity游戏排行榜的制作与优化

    游戏排行榜是一个很重要的功能,在弱联网的单机游戏与网络游戏中排行榜都是非常重要的,今天我们来详细的讲解游戏排行榜的制作方案,主要有4个点: 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交 ...

  3. Unity游戏优化指南大全(持续更新中!)

    Unity游戏优化指南大全 三个官方优化提示: 性能和优化 (Performance and Optimization) - 关于性能分析器以及性能和优化技巧的 Unity 学习教程. Best pr ...

  4. Unity游戏优化(第2版)学习记录8

    Unity游戏优化[第二版]学习记录8 第8章 掌握内存管理 一.Mono平台 1.垃圾回收 2.内存碎片 3.运行时的垃圾回收 4.多线程的垃圾回收 二.代码编译 三.分析内存 1.分析内存消耗 2 ...

  5. Unity游戏优化[第二版]学习记录6

    以下内容是根据Unity 2020.1.01f版本进行编写的 Unity游戏优化[第二版]学习记录6 第6章 动态图形 一.管线渲染 1.GPU前端 2.GPU后端 3.光照和阴影 4.多线程渲染 5 ...

  6. Unity手机游戏发热发烫优化指南与技巧

    Unity手机游戏发热发烫优化指南与技巧 很多小伙伴做完游戏后,发布到Android,运行,游戏很流畅,也不卡顿,但是跑一会游戏,手机就发热,发烫.客户提出需求,能否让它不发烫? 本文从以下3方面来分 ...

  7. 《Unity游戏优化》第三章 批处理

    动态批处理.静态批处理.GPU Instancin 和SRP Batcher 批处理原理 批处理设置 Frame Debugger 1. 动态批处理 动态批处理使用场景 动态批处理优势: 动态批处理缺 ...

  8. Unity游戏优化[第二版]学习记录4

    Unity游戏优化[第二版]学习记录4 第4章 着手处理艺术资源 一.音频 1.导入音频文件 2.加载音频文件 3.编码格式与品质级别 4. 音频性能增强 二.纹理文件 1.纹理压缩格式 2.纹理性能 ...

  9. 【游戏优化】AOI算法、Unity游戏优化(一)

    Unity游戏优化.内存优化.资源优化.AOI算法.安全 AOI概念和设计 地图广播(地图消息同步) AOI解决的问题 AOI的设计 场景分析与方案设计(一) 改善方案 场景分析与方案设计(二) 场景 ...

最新文章

  1. android webview mailto,Webview email link (mailto)
  2. 买卖股票 状态机模型的理解
  3. destoon php,DESTOON_7.0_UTF8
  4. Maven+struts2+spring4+hibernate4的环境搭建
  5. MongoDB 常用故障排查工具
  6. filestream 生成xml 文件时被如何让禁止转义_从Edgecam到PCDMIS,如何将工艺工程师的思想加入质量检测?...
  7. 微软系统修复工具(试用版)
  8. SQLAlchemy 增删改查
  9. java服务端异步处理机制_Java异步处理机制实例详解
  10. python中绝对值怎么表示_python如何使用绝对值
  11. 什么是敏捷开发(Scrum)?
  12. android 倒水动画,Android 模拟圆形水杯倒水的效果
  13. granger Z-score问题
  14. 有穷自动机的最小化c语言代码,无符号数有穷自动机实现.doc
  15. PHP银联在线支付接口开发日志
  16. 图形化开发(一)——Three.js基本介绍-优缺点-在线编辑器 Babylon.JS是最好的JavaScript3D游戏引擎
  17. Linux下编写贪吃蛇游戏
  18. 2022危险化学品经营单位安全管理人员考试题库及在线模拟考试
  19. 用C语言编写一个无限循环语句
  20. 中软实习培训记录二(0721)

热门文章

  1. iphone电压测试软件,新款iPhone SE充电兼容性大测试之45W篇
  2. 一个有趣的时间段重叠问题
  3. mysql 复制数据库
  4. X-Cash空投领取教程
  5. u-boot 自定义命令
  6. window.open 在Safari中被拦截
  7. 使用excel公式vlookup提取多个表中的数据
  8. swiper vue 切换到指定_vue+swiper实现背景跟随轮播图切换
  9. 计算机与应用在线作业答案,计算机应用基础在线作业及答案
  10. 数字摄影测量之特征点提取算法