type
status
date
slug
summary
tags
category
icon
password
主要介绍笔者多年开发经验下,总结的注意事项,和一些简单的优化点。
文章原创,转载需标明出处
📝 注意事项
🤗 优化小技巧
async UniTask状态机对象优化
在游戏开发中,如果是c#异步编程的情况下会经常用到
async
和UniTask
,可能有人角色UniTask是零GC的。UniTask内部确实都是走的对象池,但是遇到一个async方法就会生成一个状态机对象,这个对象是编译器生成,并且创建的。所以一个async就会有一个状态机对象,但是有些场景是可以优化掉的。例如:
这种不需要在Test方法内的await完成之后做一些事情,是可以完全可以避免这个状态机对象的创建,优化方式如下:
PS:Unity中请不要在声明函数的时候混用async void和async UniTask。因为AsyncUniTaskMethodBuilder会帮忙捕获UniTask的取消异常,而async void需要自己捕获。所以使用async UniTask声明函数来说更为方便,GC也更少。使用AutoResetUniTaskCompletionSource来扩展UniTask的时候也会更加的方便,直接调用TrySetCanceled就可以让await之后的代码不执行,如果是在async void声明的函数中,这样会引发task的取消异常。
foreach遍历的问题
当foreach遍历接口类型的数据的时候,例如IList,IDictionary等数据的接口的时候,会有GC Alloc,因为接口类型返回的迭代器的接口GetEnumerator返回的也是接口类型,接口类型是引用类型,而Enumerator是结构体,涉及到装拆箱。如果是实例List会直接调用返回结构体实例Enumerator,所以没有GC。
Benchmark代码和结果如下,对比的是List和IList,Dictionary和IDictionary同理
可以看到Test2不仅速度慢,并且还有GC Alloc
String Concat 性能相关
string的连接是编程中常见的操作,但是不同的方式结果也是不同。对于少于4个的字符串需要连接的,直接使用+就可以,string的+本质就是string.Concat,这点可以从编译后的IL代码看出。
string.Concat的效率已经很高了,本质是调用FastAllocateString生成好需要长度的string,然后使用unsafe往里面填充内容。但是超过5个string需要连接的时候,调用的是Concat(params string?[] values),这里一看参数就知道不是很妙。所以以下来测试以下5个字符串的连接,5个以上同理。
- Test1和Test2因为本质是一样的,所以速度没有区别,效率比较低,GC Alloc也多。因为用的可选参数,实现的代码量复杂一些。
- Test3使用stringbuilder,并且开始的时候一次扩充好容量,效率很高,GC Alloc也是最少的之一。
- Test4使用两个string.Concat,因为都不超过4个参数,走的是固定的参数的string.Concat的重载,效率最高,但是因为多创建了一个无用的string,所以GC Alloc也比较高。如果C#能够暴露FastAllocateString函数的话,就可以照葫芦画瓢多写个一些string.Concat的重载来加快效率。这样速度和GC都能是最优的。
- Test5是用的网上比较出名的ZString库,虽然GC Alloc也是最少的之一,但是实现上用的泛型,然后里面if判断类型是string和int,并且还有try-finally,整体执行效率不高。内部实现原理是使用的动态扩容的char[],然后char[]走的ArrayPool的对象池。
- Test6和Test7都是Format,其中$”” format的方式比直接string.Format更优。$””的代码其实通过IL代码来看会转换为DefaultInterpolatedStringHandler,比较类似ValueStringBuilder,但是实际代码会复杂多,因为用的泛型函数AppendFormatted<T>(T value),内部判定接口的代码比较多,效率会低一些。当然string.Format的效率是最低的。
PS: 上述测试都是基于参数是string的情况下,如果参数是int、float等,使用时切记要ToString转成string,避免传参到object,然后涉及装拆箱就效率更低了。
综上所述,超过5个字符串参数连接时,使用StringBuilder在时间和GC上相对来说是比较优的,前提是StringBuilder对象本身也要复用
补一下少于5个字符串参数时的性能对比,推荐直接使用+号连接就行了。
再补充一下没有ToString,直接Format的性能对比。
使用stackalloc在栈上创建临时数组
众所周知C#中的数组是需要在堆上创建,受GC管理的,这样如果创建的多,会给GC造成压力,有没有方法是可以在栈上创建避免GC呢。
这个是有的,可以使用stackalloc的方式在栈上创建数组,但是要注意,离开作用域会自动释放,不能继续持有指针。当然在作用域内也可以传入别的函数进行操作的。这点和C++中创建一个结构体对象,然后用&获取指针使用很相似。
使用案例+Benchmark如下,可以看到是比new int这样创建数组性能更优的。
- Author:有理fan
- URL:https://unifan.top/article/attention_optimize1
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts