Jos*_*eph 42 javascript performance
我正在编写一些需要快速运行的javascript代码,并使用了许多短期对象.我最好使用对象池,还是仅仅根据需要创建对象?
我写了一个JSPerf测试,它表明使用对象池没有任何好处,但是我不确定jsperf基准测试是否运行得足够长,以便浏览器的垃圾收集器能够启动.
代码是游戏的一部分,所以我不关心传统的浏览器支持.无论如何,我的图形引擎无法在旧浏览器上运行.
Dom*_*omi 49
首先让我说:我会建议不要使用池,除非你正在开发可视化,游戏或其他计算成本高昂的代码,这些代码实际上做了很多工作.您的普通Web应用程序受I/O限制,您的CPU和RAM大部分时间都处于空闲状态.在这种情况下,通过优化I/O而不是执行速度,您可以获得更多收益; 即确保,您的文件加载速度快,您采用客户端而不是服务器端渲染+模板.但是,如果您正在玩游戏,科学计算或其他受CPU限制的Javascript代码,那么本文可能对您有意义.
简短版本:
在性能关键代码中:
Array.concat等)都会做很多邪恶的事情,包括分配.String),因为这些将在您对它们执行的状态更改操作期间创建新对象.长版:
首先,考虑系统堆与大对象池基本相同.这意味着,当你创建一个新的对象(使用new,[],{},(),嵌套函数,字符串连接等),系统将使用一个(非常精密,快速,低级别的性能优化)算法来给你一些未使用的空间(即一个对象),确保它的字节被清零并返回它.这与对象池必须做的非常相似.但是,Javascript的运行时堆管理器使用GC来检索"借来的对象",其中池以几乎零成本的形式返回它的对象,但是需要开发人员自己负责跟踪所有这些对象.
现代Javascript运行时环境(例如V8)具有运行时分析器和运行时优化器,当它识别性能关键代码段时,理想情况下可以(但不一定(尚未))进行积极优化.它还可以使用该信息来确定垃圾收集的好时机.如果它意识到你运行游戏循环,它可能只是在每几个循环后运行GC(甚至可能将老一代集合减少到最小等),从而实际上不会让你感觉它正在做的工作(但是,它仍然会如果是昂贵的操作,请更快地耗尽电池).有时,优化器甚至可以将分配移动到堆栈,这种分配基本上是免费的,而且对缓存更友好.话虽这么说,这些优化技术并不完美(实际上它们不可能,因为完美的代码优化是NP难的,但这是另一个话题).
让我们以游戏为例:JS中关于快速矢量数学的讨论解释了重复的矢量分配(在大多数游戏中你需要大量的矢量数学)会减慢一些应该非常快的东西:矢量数学用Float32Array.在这种情况下,如果您以正确的方式使用正确的游泳池,您可以从游泳池中受益.
这些是我在Javascript中编写游戏时学到的经验:
代替
var x = new X(...);
Run Code Online (Sandbox Code Playgroud)
使用:
var x = X.create(...);
Run Code Online (Sandbox Code Playgroud)
甚至:
// this keeps all your allocation in the control of `Allocator`:
var x = Allocator.createX(...); // or:
var y = Allocator.create('Y', ...);
Run Code Online (Sandbox Code Playgroud)
这样,您可以先实现X.create或Allocator.createX使用return new X();,然后在以后用池替换它,以便轻松地比较速度.更好的是,它允许您快速查找代码中的所有分配,以便在时机成熟时逐个查看.不要担心额外的函数调用,因为任何体面的优化器工具都会内联,甚至可能由运行时优化器内联.
代替:
function add(a, b) { return new Vector(a.x + b.x, a.y + a.y); }
// ...
var z = add(x, y);
Run Code Online (Sandbox Code Playgroud)
尝试:
function add(out, a, b) { out.set(a.x + b.x, a.y + a.y); return out; }
// ...
var z = add(x, x, y); // you can do that here, if you don't need x anymore (Note: z = x)
Run Code Online (Sandbox Code Playgroud)
避免:
var tmp = new X(...);
for (var x ...) {
tmp.set(x);
use(tmp); // use() will modify tmp instead of x now, and x remains unchanged.
}
Run Code Online (Sandbox Code Playgroud)
new(因为运行时可以完全控制如何分配东西).在计算循环紧张的情况下,您可能希望考虑每次迭代进行多次计算,而不是仅考虑一次(也称为部分展开循环).池算法
除非你编写一个非常复杂的池查询算法,否则你通常会遇到两个或三个选项.这些选项中的每一个在某些情况下都更快,在其他情况下更慢.我经常看到的是:
inUse标志设置为true.不再需要该对象时取消设置.玩这些选项.除非您的链表实现相当复杂,否则您可能会发现基于数组的解决方案对于短期对象(这是池性能实际上很重要)更快,给定,数组中没有长寿命对象,导致搜索自由对象变得不必要地长.如果您通常需要一次分配多个对象(例如,对于部分展开的循环),请考虑批量分配选项,该选项分配(小)对象数组,而不仅仅是一个,以减少未分配对象的查找开销.如果你真的很喜欢快速池(和/或只想尝试一些新的东西),那么看一下如何快速实现系统堆并允许不同大小的分配.
最后的话
无论您决定使用什么,继续进行剖析,研究和分享成功的方法,使我们心爱的JS代码运行得更快!
Dan*_*Tao 13
一般来说(根据我的个人经验),汇集对象不会提高速度.创建对象通常非常便宜.相反,对象池的目的是减少垃圾收集引起的jank(周期性滞后).
作为一个具体的例子(不一定是JavaScript,但作为一般说明),想想具有高级3D图形的游戏.如果一个游戏的平均帧速率为60fps,则比另一个平均帧速率为40fps的游戏更快.但是如果第二个游戏的fps 一直是40,那么图形看起来很平滑,而如果第一个游戏的fps 通常远高于60fps但偶尔会下降到10fps,那么图形看起来就会变得不稳定.
如果您创建一个运行两个游戏10分钟的基准测试并且每隔一段时间对帧速率进行采样,它就会告诉您第一个游戏具有更好的性能.但它不会在波动中发现.这就是对象池要解决的问题.
当然,这不是涵盖所有案例的一揽子声明.当您经常分配大型阵列时,池化不仅可以改善波动性而且还可以改善原始性能的一种情况是:通过简单地设置arr.length = 0和重用arr,您可以通过逃避未来的重新分析来提高性能.类似地,如果您经常创建非常大的对象,这些对象都共享一个公共模式(即,它们具有明确定义的属性集,因此您不必在将每个对象返回到池时"清理"它们),在这种情况下,您可能会看到池中的性能提升.
正如我所说,一般来讲,虽然,这不是对象池的主要目的.