【译】利用xperf解决unity导入纹理缓慢的问题
原文地址:http://blogs.unity3d.com/2015/01/10/curious-case-of-slow-texture-importing-and-xperf/
原文作者未做版权声明,视为共享知识产权进入公共领域,自动获得授权
我曾看到过一个缺陷报告:当前Unity5.0导入纹理过于缓慢。其中一个纹理的导入花费了其平常耗费的10倍时间。
为什么纹理导入会如此慢?这份缺陷报告让我第一次使用了xperf(又名Windows性能工具包)。
一、回顾一下故事的过程
我们得到一个案例报告,在当前Unity5.0测试版中一个TGA格式的纹理(2048*2048,未压缩,一个12M的文件)的导入花费了10秒,但是在Unity4.6版本中只需要花费大约1秒。
第一个猜测:是不是有人不小心禁用了纹理的多线程压缩?
第二个猜测:我们使用FreeImage库来导入纹理。也许谁提交了调试版本?
首先,我们将使用profiler检测问题所在。
我们发现所有的时间都很奇怪花在了WinAPI ReadFile函数上?!还是测试的TAG文件有一下特殊?让我们重新使用一个同样大小,未被压缩的PNG文件再次尝试。
PNG的导入花费了108ms,而TGA花费是9800ms。(我已经禁用了DXT压缩,以保证只关注导入时间。)在Unity4.6中,同样的测试是116ms(PNG)和310ms(TGA)。文件大小大致相同。
二、使用xperf
于是我咨询一位深谙Windows的同事:“为什么读取一个文件,ReadFile函数会花费如此多的时间,但是另一个同样大小的文件却读取非常快?” 他给出答案,“你应该尝试使用xperf来看看”。
配置Windows性能记录仪,勾选上“CPU usage”、“Disk I/O activity”和“File I/O activity”,然后点击“Start”。
在Unity中导入纹理,点击“Save”,然后“Open in WPA”按钮.
侧栏给出了各项指标的统计图。一个诡异的现象是:无论是CPU还是存储的图都没显示出剧烈的活动?于是感觉问题变得棘手起来!。
三、CPU使用情况分析
双击Computation的图,显示出每一个进程的CPU使用时间曲线。查看Unity.exe在图中高亮的那段时间内的CPU占用情况。
四、查看I/O使用
还记得这个侧栏中的“Storage”图没有显示剧烈的活动?现在,我们来展开它从而得到更多的细节信息。
突然发现,有点眉目了!当我们导入TGA文件的时候,“File I/O”的总图上显示出了剧烈的活动。现在只需要来搞清楚到底发生了什么。
双击侧边栏的图来获取更详细的I/O信息:
现在您也许看到了那里发生了什么。我们读取了一堆文件,实际上约有400,000之多。
同CPU部分一样,工具是通过左侧的黄色的分配器的信息来组织列信息。把“Process”拖拽到左侧;下图表明了所有的这些读取的确都是由Unity触发的。
展开来看看谁是真正的罪魁祸首:
我们正在阅读这些文件,一次读取3个字节。
五、缺陷是怎么出现?怎么解决?
但是为什么我们三个字节三个字节的读取一个12M的TGA文件?我们已经好久没更新过图片文件读取的库了,那么,这些缺陷是哪来的?
在代码中找到我们读取进FreeImage的地方。看来我们正在建立自己的I/O函数,然后给FreeImage使用。
历史版本检查:果然,一些星期前,这段代码发生了一些改变,从更为基础的“你好,从这个文件路径加载图片”变成了“你好,使用这些I/O回调函数加载图片”
一般这些改动都是有意义的的。如果我们有自己的文件系统函数,然后合理的使用,就可以读取一些非普通文件。(例如存档或者压缩文件等。)在这个案例中,我们是为了支持在光照贴图上缓存LZ4压缩。(FreeImage不需要知道他们头部是否存在LZ4压缩,而能直接导入纹理文件。)
一切都如此美好。但是这个改变却导致了一个完全意外的性能缺陷。
当你不直接传递I/O例程到FreeImage,他会使用C的标准输入输出作为默认设置。
C的标准例程默认做I/O缓冲……我们的例程没做。所以FreeImage的TAG加载函数做了大量的读取操作,一次读取一个像素。
六、解决方案
一个适当的解决方案是给FreeImage设置一个带有缓冲的I/O例程。
需要确认的是,是否这就是真正的罪魁祸首,从而不会再出现“TGA文件导入太慢“,我做了一个修补程序,直接读取整个文件到内存,从内存进行加载。
一次读取整个图片到内存好吗?视情况而定,我猜测95%的情况下是好的,尤其64bit的Unity编辑器。对于大多数未压缩的图像文件,最终数据大小会比文件大小大很多。也许唯一的例外是.PSD文件,它会携带很多层信息,但是我们只对“复合图像”节点感兴趣。所以,这也是为什么我说“修补程序”。一个妥善的解决方案是支持具有缓冲的I/O例程,又或者升级。
这样导入TGA和PNG会比以前快很多:TGA花费75ms,PNG花费87ms。(Unity 4.6中TGA是310ms,PNG是116ms,当前测试版中TGA是9800ms,PNG是108ms。)
七、结论:
当使用你自己的实现来取代内置的函数库时需要谨慎。(例如标准的输入输出,内存分配,日志,或者……其他函数库的例程。)他们可能会有不同的性能特性。
在windows下xperf非常有用。如果感兴趣,可以查看Bruce Dawson博客。
Mac上,Apple’s Instruments是一个类似工具。我应该会在以后的博客中使用到它。