针对一个渲染引擎的性能,最主要的当然是实时渲染的帧率。另一方面,对于轻型的引擎来说,用户不会对场景加载
保有一个较长的加载时间的心理预期,最好是,点击按键,瞬间整个场景出现,毕竟轻量应用场景不是重型游戏。针对
这种需求,我们需要对资源加载做一个全面的性能排查与优化。
根据《Unix编程艺术》中的描述,性能优化最好是不要优化,如果要优化那么花费一点时间把 bottle neck 找到是很有
必要的。所以在我的优化工作中,我首先对整个系统做了较为完整的 profile. 我大致将整个资源加载部分逻辑分成了
自顶向下的三层:
- meshrender 层:整个场景存在若干个 meshrender,该层级统计各个 meshrender 的加载时间
- resource 层:每个meshrender 依赖若干资源:mesh, material, skin/animation 等等
- detailed resource 层:对每个 resource 的详尽的分部统计,不同的 resource 步骤也不同
对我们的整个引擎的加载部分,分层级自上而下统计,对每个 meshrender 统计得到的情况如下表,其中分别有几个模块:
Mesh Material Skin 各占一部分,同时图片的加载已经使用了异步线程加载方式,故不统计在总时间内(加载图的子
线程在主加载线程完成任务之前就已经完成结束)。从下表数据看,Mesh 与 Material 均占较大部分的比例,所以在下面
detailed 层,专门针对这两个部分继续剖析。
Detailed 分析分别对几个不同的模块进行了分析。其中 Mesh 的加载虽然耗时较长,但是实际时间绝大部分用在将 mesh
数据从文件中读取到内存,并没有发现其他可能浪费时间的耗时操作,逻辑上也比较简单,故结论是无优化空间。
另外 Material 部分,其逻辑就比单纯加载一块 mesh 的buffer 要复杂许多了。其中包括了特定对象 MaterialAsset 的
创建与初始化,shader 的加载以及解析,启动异步读图线程等等。具体的耗时分布如下表所示。
其中 shader 部分是耗时大头,此处 shader 并不仅仅包括 shader 文件的加载,还有其中一些 uniform
变量的解析,
探究其逻辑发现两点
- shader 文件读取存在一次多余的 copy
- 其中有一个超过20个 case 的 switch 语句
针对这两者情况我都做了相应的优化
- 使用数据指针代替原有的
std::vector<>
在传递时几乎没有损耗 - 使用查表的方式替代 switch-case 语句,提高性能同时也提升代码质量,减少圈复杂度,同时更便于维护和更新
优化以后的数据如下表
在 Skin 的加载部分同样找到了部分多余的值 copy 操作,优化为引用后数据略有提升。
总结最后我们来看一下总体的性能提升情况
Performance of resource loading
Mesh Material Skin Others
before opt xxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxx|x|
after opt xxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxx|xx|x|