C#中关于字符串的优化
种种原因,好久没写博客了(主要是懒),然后感觉有必要对工作中遇到的一些问题和学到的知识做下总结和积累,所以我决定重新开始养成写博客的好习惯!之前博客内容主要是个人兴趣,所以大多数都是和Shader相关的。今天开始我的博客内容可能会偏向于代码和Unity引擎,因为我现在工作的内容就是引擎技术应用,会做一些基础的,底层的东西。
废话不多说,开始说正事。
遇到的问题
我为什么想要写字符串优化呢?主要是最近遇到这么一个问题: 我们用**Dictionary<string, string>**去存储Asset到AssetBundle的映射关系,这样好处就是查找方便,但是一旦项目上了规模,由于有大量的string,这一块就会遇到内存占用过多的问题。
解决方法
一,使用string.Intern()
说到string,首先就能想到同样内容的string复用问题。我们都知道C#中存储string用了一个叫驻留池的东西,当定义一个string时,如果驻留池中存在我们想要的内容,那么我们就可以把池中的引用赋值给我们新的string,不需要开辟新的内存。但是什么时候会去驻留池中找呢?首先所有字面声明的字符串如**string str = "a"会自动的去池中找,其余的通过拼接,函数返回等方式获取的字符串都不会自动去池中查找,这时我们就可以使用string.Intern()**方法来手动的让字符串去驻留池中查找内容。由于字典中存储的AssetBundle名字会有大量重复,这样做确实能节省下一部分内存开销。
二,使用byte[]代替string
C#和C++不一样,C#中的char类型大小为两个字节,也就是说我们用char[]或者string存储纯英文内容,至少有一般内存空间是被浪费掉的,所以用byte数组来存储纯英文内容可以节省不少内存空间,虽然这样每次使用都需要再转成string,但是经我测试(cpu为i7-9700),调用**System.Text.Encoding.UTF8.GetString()**函数传入长度大概为60的byte数组10000次耗时5ms,会有2MB的GC产生,在可接受的范围,不知道这方面还有什么更好的优化方法吗,先留个坑。
三,使用hash代替string
之前提到的字典Key值是Asset路径,实际上可以使用hash来代替,如果遇到冲突的再单独使用string来存储,实际上一个uint的hash值就够了,我测试了51000个Asset路径,用自己的方法计算出32位的uint哈希值,并没有遇到冲突。就算有冲突也是很少的一部分,单独存储也不会有多少开销。
所以我最后是用了一个Dictionary<uint, ushort>,一个**Dictionary<string, ushort>和一个byte[][]**来代替了之前的字典,byte[]数组存储的是AssetBundleName,两个字典的Value存储的是这个数组的索引,这样也不会存在同样的AssetBundle占多分内存的问题了,最终使用51000个Asset对应16000个AssetBundle来测试,只占用了大概1.7MB内存,优化效果还是很可观的。