最近在做游戏引擎性能优化,关于js执行性能有些内容拿来这里分享。
首先需要明确两点:
第一,本文讨论的js性能问题,都是在大量执行的情况下才暴露出来的。一般来说,60fps的游戏,如果每帧需要执行2000次以上,那么就可以考虑本文的优化思路了。如果执行频次没有达到以上量级,性能并不会有明显提升。
第二,得到的这些性能红利在某些情况下需要牺牲代码结构与可读性,考虑在实际项目中是否值得。很多时候,需要牺牲一点点性能来使你的代码更容易维护。切忌对项目过度优化。
以下几点,已经证明在大量执行的情况下,会对项目性能产生影响。
设计模式和重构原则经常告诉我们:
一个函数只做一件事,如果几个函数都在做一件事,那么抽象出一个对象
是的,这些原则在业务逻辑开发中很有用,让代码更加容易维护,同时对性能几乎没有什么影响。但是,某些情况下它并不是毫无性能开销的。当执行太多的函数调用后,你会发现性能变得很慢。每当我减少一些函数调用(把函数中的代码直接拷贝到当前调用位置),性能都会有提升。
例如,在渲染引擎中经常会有矩阵计算,我们一般会抽象出一个matrix类:
matrix1.append(matrix2) 复制代码
来实现矩阵相乘,但如果这段代码是在主渲染循环中,你可能就要考虑展开成如下的样子:
matrix1.a = matrix2.a * matrix1.a + matrix2.b * matrix1.c; // ... 复制代码
这样性能真的会有一些提升(大量执行的情况下),如果你的函数调用路径过长,建议进行一些流程简化。
除非极特殊的情况,要从架构上减少函数调用,不要粗暴地拆散函数。
js在查找变量的时候,会从当前作用域开始依次向上层查找。因此可以推断出,局部变量的访问永远是最快的,全局变量的访问永远是最慢的。
如下例:
var array = []; function getName() { array[0] = 0; array[1] = 0; array[2] = 0; array[3] = 0; // ... } 复制代码
性能会低于:
var array = []; function getName() { var array = array; array[0] = 0; array[1] = 0; array[2] = 0; array[3] = 0; // ... } 复制代码
如果在一个函数中多次调用全局变量或外层变量,记得先把它保存到局部变量。
多次调用this关键字会让你的js执行很慢。因为当你访问一个对象属性,js执行时就要去查找原型链,直到查找到该属性为止。这个开销是很大的。
下面的例子:
for(var i = 0; i < 100; i++) { this.array[i] = i; } 复制代码
性能低于:
var array = this.array; for(var i = 0; i < 100; i++) { array[i] = i; } 复制代码
如果多次访问对象属性(甚至循环调用),建议先把该属性保存到一个局部变量中,再使用。
js的四则运算中,除法是最慢的,乘法其次。Math封装的数学函数中,sin与cos函数执行是最慢的。
下面的例子:
// a在大部分情况下为0 c = a * b; f = a * e; 复制代码
性能低于:
// a在大部分情况下为0 if(a == 0) { c = 0; f = 0; } else { c = a * b; f = a * e; } 复制代码
尽量避免不必要的乘除运算,可能的情况下,缓存sin和cos运算结果。pixi.js中,显示对象的旋转要用到三角函数计算,引擎内部进行了标脏处理。egret中,对全局的三角函数计算方法进行了查表优化
。
在主循环方法中仔细查找,项目中可能存在很多类似的可优化点。
大量调用数组push与pop方法,如果这些调用出现在循环中,那么很不幸,它会造成性能的下降。
如果在你的渲染循环中有这样的结构:
var matrix = Matrix.create(); // do something Matrix.release(matrix); 复制代码
并且对象池是这样实现的:
// 创建matrix对象 Matrix.create = function() { return Matrix.pool.pop() || new Matrix(); } // 释放matrix对象 Matrix.release = function(matrix) { Matrix.pool.push(matrix); } 复制代码
那么你的主循环会被push与pop拖慢速度。
一般来说,引擎中会大量使用的临时对象。特定情况下,对于临时对象,除了使用对象池,还可以用一个全局的temp对象替代:
例如,上例中,我们可以定义一个用于引擎内部临时使用的matrix对象 $tempMatrix:
Matrix.$tempMatrix = new Matrix(); 复制代码
使用的时候只需要:
var matrix = Matrix.$tempMatrix; // do something matrix.identify(); 复制代码
细心的同学可能会说,这样重复使用一个对象,如果代码逻辑上需要多个temp对象,上面的实现就不能解决需求了。的确,那我们就需要用其它的方式来解决。不过对于特定情况,上面这种优化是简单有效的。
总结来看,这些所谓的性能优化点,大部分都是js语言在运行过程中的弱点,在其它语言中未必会重现。例如,上文提到的函数调用,在c++等语言中并不会对性能造成明显影响。
另外,如果大家做的不是类似于引擎这样的底层产品的话,这些东西了解一下也就得了。
再次强调,永远不要过度优化!!!