怎样才算一个合格的程序员?
我见过一些刚来的实习生小弟,编码注重细节,注释到位,也见过一些所谓的大牛对项目规范置若罔闻,随心所欲把项目结构搞得乱七八糟。
前几天看到过一篇文章提到一句话:编码是在创造价值,在这之间所欠的技术债务是在削减你的代码价值,当你编码完成还要花更多的时间去偿还的时候,那还不如不写。当然帮你偿还的人不一定是你自己而已。
书回正传,代码效率在项目表现中的重要性不言而喻,因为由它引起的卡顿,闪退,发热,等等问题,会让项目体验达到非常糟糕的地步。那么就从自己身上开始,从细节提升自己吧。
有哪些需要谨慎操作的东西呢?
1. new 操作
new 操作非常非常非常耗费时间,尤其是在循环里边做这件事,一定会让人对你“肃然起敬” ,你不怕被骂就试试看.......
new操作都干了什么:
1.1 计算类型及所有基类中定义的所有实例字段所需要的字节数,并附加类对象指针(type object pointer)和同步块索引(sync blovk index),以便CLR用来管理对象,他们会产生额外的字节数。
1.2从托管堆中申请分配1.1计算所得的字节数,并把所有内存值重设为0.
1.3初始化类对象指针和同步块索引成员。
1.4 调用类型的实例构造方法,传入构造函数所需的实参,并且根据继承关系调用父类的构造方法。
建议:
需要用到的对象或容器,尽量在用之前就new 好,避免在循环或其他场合重复进行new操作。一句话,减少new操作的次数。
2. 字符串操作
2.1 string.empty 和 “ ”
字符串初始化或赋空值使用String.empty替代,避免为你写的 “ ”分配额外内存。
2.2 字符串拼接
+号拼接字符串是那么的方便,但要注意:在操作的字符串长度很小且个数少时,C#会自动优化为stringBuilder 的拼接方式,这时候对代码性能影响不会太大。当字符串长度增加并且个数增加时,操作的字符串单位就会产生额外的内存消耗,所以尽量养成用stringBuilder 去组装字符串的习惯。
2.3 用string.isnullOrEmpty 代替 strvalue == “ ” 或 null
2.4 使用string.compare()取代 == 操作
3. CLR反射
有关程序及其类型的数据被称为元数据,它们保存在程序的程序集中。而程序在运行时,可以查看其他程序集或其本身的元数据。
一个运行的程序查看本身的元数据或其他程序的元数据的行为叫做反射。
反射的性能问题没什么聊得,存在即合理,要考虑的是如何降低反射调用的消耗而已。反射中读取的对象以及对象的方法、字段,可以通过缓存的方式存起来,重复调用就可以省下不少时间。
4. 优先使用foreach循环语句 替代for()
4.1 foreach可以消除编译器对for循环对数组边界的检查;
4.2 foreach的循环变量是只读的,且存在一个显式的转换,在集合对象的对象类型不正确时抛出异常;
4.3 foreach使用的集合需要有:具备公有的GetEnumberator()方法;显式实现了IEnumberable接口;实现了IEnumerator接口;
4.4 foreach可以带来资源管理的好处,因为如果编译器可以确定IDisposable接口时可以使用优化的try…finally块;
除了安全性效率也很重要:
2017/8/4 加入对for & foreach & iterator 遍历方式的效率测试数据:
测试样本:分别5000000个值类型Array、值类型list、引用类型Array、引用类型list
遍历方式:for 循环、foreach循环、iterator
数据量:5000000
5000000 for foreach iterator
值类型 Array 156250 234375
值类型list 312500 468750 390625
引用类型Array 156250 312500
引用类型list 312500 625000 468750
结论: 对于值类型的array for > foreach
对于值类型的ist for > iterator> foreach
对于引用类型的array for > foreach
对于引用类型的ist for > iterator> foreach
5. 尽量减少装箱和拆箱
5.1 关注一个类型到System.Object的隐式转换,同时值类型不应该被替换为System.Object类型;
5.2 使用接口而不是使用类型可以避免装箱,即将值类型从接口实现,然后通过接口调用成员。
笔记先记这些吧,慢慢再补充....
二更,当前项目的目标是登上腾讯的大船,所以在项目编码中要以腾讯tdr标准作为参考条件,补上一些Unity制作过程中的执行细节:
* GC Alloc和卡顿
* 不要在运行时大量使用LINQ
* 不要每帧都有GC Alloc
* C#里用二进制替代json、xml等解析文本
* 使用GameObject.GetComponents*的GC Alloc友好的重载
* 不要在运行时大量使用反射
* 不要在UI初始化时遍历查找GameObject,在做预制的时候序列化好
* 不要在运行时大量使用List<T>.ToArray
* 用Dictinary<KEY, VALUE>.TryGetValue取代Dictinary<KEY, VALUE>.ContainsKey和Dictinary<KEY, VALUE>.[]的组合
* 如果枚举作为Dictinary<KEY, VALUE>的key需要提供实现了IEqualityComparer的comparer
* List.Remove和Dictionary.Remove(需要参数非空否则抛异常)没必要调之前先List.Contains或者Dictionary.ContainsKey
* 不要在运行时大量使用foreach
* 对WaitFor*进行缓存
* 用GameObject.CompareTag取代GameObject.tag ==
* 使用Physics的NonAlloc版本的API
* Input.GetTouch取代Input.touches,Input.GetAccelerationEvent取代Input.accelerationEvents
* 不要以数组下标遍历SkinnedMeshRenderer.bones,Renderer.materials和sharedMaterials
* 不要在运行时大量使用Object.FindObjectOfType和Object.FindObjectsOfType
* GameObject.Find建议传完整路径
* 拼接string用StringBuilder
* string.StartsWith和string.EndsWith和string.IndexOf和string.Compare和string.Equals和string.LastIndexOf使用可以传StringComparison.Ordinal参数的重载
* 不要在运行时大量使用闭包
* List在大量Add但不能AddRange前提前扩容capacity
* List在大量Remove但不能RemoveRange从后往前Remove
* Dictionary构造时传入容量,如果可能的话
* 如果容器里存值类型,尽量不要使用System.Collections命名空间的类,使用System.Collections.Generic里的
* 尽可能重用容器
* 如果在运行时大量Instantiate和Destroy,应考虑pooling
* 不要在运行时大量使用携程
* 非要用反射,请注意FieldInfo和MethodInfo的缓存
* C#里非要用json解析,请考虑使用JSONUtility
* 减少数学计算的指令数
* 不要在运行时大量使用Debug.Log*
* 不要在运行时大量使用MonoBehaviour.*Update
* 不要在运行时大量使用delegate的"+="和"-="来注册监听和撤销监听
* 不要在运行时大量使用Enum.GetValues来遍历枚举
* 应确保去除了用不到的shader variants
* 应在适当场合做shader prewarming
* 应在适当场合做GC.Collect
* 最好是只加载界面初始就能看见的,其他的到了需要显示的时候加载
* 内存
* 如果有较大assetbundle,请不要使用lzma压缩格式
* 不需要readable的texture就不要开启read/write enabled
* UI贴图应关闭mipmap
* 不需要readable的model就不要开启read/write enabled
* 如果normals和tangents用不到,应在导入设置或者在player settings里去除
* 应确保内存中不存在漏删的资源
* 应确保assetbundle里没有重复的资源
* 应确保大多数的纹理都用了压缩格式
* 应确保贴图尺寸合理
* 应确保mesh面数合理
* 根据不同场合加载AnimationClip,比方说大厅和战斗
* fps
* 尽量减少MeshCollider
* 合适的情况下是否使用了static batching和dynamic batching
* UI尽量用atlas
* UI尽量动静分离
* UI尽量避免两个材质的穿插
* 使用合理的光照方案
* 使用合理的后处理
* 避免使用Unity自带的地形系统
* 合理的骨骼数
* 合理的半透明物体占屏幕面积