在微调性能时,多次调用 JavaScript 方法的最佳方式是什么?
我一直在研究 JavaScript 性能。我了解到,当多次访问时,通常最好将闭包变量和类成员复制到本地范围以加快速度。例如:
var i = 100;
var doSomething = function () {
var localI = i;
// do something with localI a bunch of times
var obj = {
a: 100
};
var objA = obj.a;
// do something with objA a bunch of times
};
我明白这一点;它为解释器添加了按名称查找值的快捷方式。当处理方法时,这个概念变得非常不清楚。起初,我以为它会以同样的方式工作。例如:
var obj = {
fn: function () {
// Do something
return this.value;
},
value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
事实上,这根本行不通。像这样访问该方法会将其从其范围中删除。当它到达 this.value 行时,this 引用了 window 对象,并且 this.value 可能是未定义的。我可以使用 objFn.call(obj) 将其范围传回给它,而不是直接调用 objFn 并丢失范围,但这比原始 obj.fn() 的性能更好还是更差?
我决定编写一个脚本来测试这一点,但得到了非常令人困惑的结果。该脚本对多个测试进行迭代,这些测试多次循环上述函数调用。每次测试所花费的平均时间被输出到主体。
一个对象是用许多简单的方法创建的。额外的方法可以确定解释器是否需要更加努力地工作才能找到特定的方法。
测试 1 只需调用 this.a();
测试 2 创建一个局部变量 a = this.a 然后调用 a.call(this);
测试 3 使用 YUI 的绑定函数创建一个局部变量来保留范围。我把这个注释掉了。 YUI 创建的额外函数调用使这种方式变慢。
测试 4、5 和 6 是 1、2、3 的副本,只是使用 z 代替 a。
YUI后面的功能是用来防止脚本失控错误的。计时是在实际测试方法中完成的,因此 setTimeouts 不应影响结果。每个函数总共被调用10000000次。 (如果您想运行测试,可以轻松配置。)
这是我用来测试的整个 XHTML 文档。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr">
<head>
<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var o = {
value: '',
a: function () {
this.value += 'a';
},
b: function () {
this.value += 'b';
},
c: function () {
this.value += 'c';
},
d: function () {
this.value += 'd';
},
e: function () {
this.value += 'e';
},
f: function () {
this.value += 'f';
},
g: function () {
this.value += 'g';
},
h: function () {
this.value += 'h';
},
i: function () {
this.value += 'i';
},
j: function () {
this.value += 'j';
},
k: function () {
this.value += 'k';
},
l: function () {
this.value += 'l';
},
m: function () {
this.value += 'm';
},
n: function () {
this.value += 'n';
},
o: function () {
this.value += 'o';
},
p: function () {
this.value += 'p';
},
q: function () {
this.value += 'q';
},
r: function () {
this.value += 'r';
},
s: function () {
this.value += 's';
},
t: function () {
this.value += 't';
},
u: function () {
this.value += 'u';
},
v: function () {
this.value += 'v';
},
w: function () {
this.value += 'w';
},
x: function () {
this.value += 'x';
},
y: function () {
this.value += 'y';
},
z: function () {
this.value += 'z';
},
reset: function () {
this.value = '';
},
test1: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.a();
}
return new Date().getTime() - time;
},
test2: function (length) {
var a = this.a,
time = new Date().getTime();
while ((length -= 1)) {
a.call(this);
}
return new Date().getTime() - time;
},
test3: function (length) {
var a = Y.bind(this.a, this),
time = new Date().getTime();
while ((length -= 1)) {
a();
}
return new Date().getTime() - time;
},
test4: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.z();
}
return new Date().getTime() - time;
},
test5: function (length) {
var z = this.z,
time = new Date().getTime();
while ((length -= 1)) {
z.call(this);
}
return new Date().getTime() - time;
},
test6: function (length) {
var z = Y.bind(this.z, this),
time = new Date().getTime();
while ((length -= 1)) {
z();
}
return new Date().getTime() - time;
}
},
iterations = 100, iteration = iterations, length = 100000,
t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body');
body.set('innerHTML', '<span>Running ' + iterations + ' Iterations…</span>');
while ((iteration -= 1)) {
Y.later(1, null, function (iteration) {
Y.later(1, null, function () {
o.reset();
t1 += o.test1(length);
});
Y.later(1, null, function () {
o.reset();
t2 += o.test2(length);
});
/*Y.later(1, null, function () {
o.reset();
t3 += o.test3(length);
});*/
Y.later(1, null, function () {
o.reset();
t4 += o.test4(length);
});
Y.later(1, null, function () {
o.reset();
t5 += o.test5(length);
});
/*Y.later(1, null, function () {
o.reset();
t6 += o.test6(length);
});*/
if (iteration === 1) {
Y.later(10, null, function () {
t1 /= iterations;
t2 /= iterations;
//t3 /= iterations;
t4 /= iterations;
t5 /= iterations;
//t6 /= iterations;
//body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>');
body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>');
});
}
}, iteration);
}
});
</script>
</head>
<body>
</body>
</html>
我已经使用 Windows 7 在三种不同的浏览器中运行了该程序。这些结果以毫秒为单位。
Firefox 3.6.8
Test 1: this.a();
9.23
Test 2: a.call(this);
9.67
Test 4: this.z();
9.2
Test 5: z.call(this);
9.61
Chrome 7.0.503.0
Test 1: this.a();
5.25
Test 2: a.call(this);
4.66
Test 4: this.z();
3.71
Test 5: z.call(this);
4.15
Internet Explorer 8
Test 1: this.a();
168.2
Test 2: a.call(this);
197.94
Test 4: this.z();
169.6
Test 5: z.call(this);
199.02
Firefox 和 Internet Explorer 产生的结果符合我的预期。测试 1 和测试 4 相对接近,测试 2 和测试 5 相对接近,测试 2 和测试 5 比测试 1 和测试 4 花费更长的时间,因为需要处理额外的函数调用。
Chrome我完全不懂,但它快得多,也许亚毫秒级性能的调整是不必要的。
有人对结果有很好的解释吗?多次调用 JavaScript 方法的最佳方法是什么?
I have been researching JavaScript performance. I've learned that when accessing more than once, it is usually best to copy closure variables and class members into local scope to speed things up. For example:
var i = 100;
var doSomething = function () {
var localI = i;
// do something with localI a bunch of times
var obj = {
a: 100
};
var objA = obj.a;
// do something with objA a bunch of times
};
I understand this; it adds a shortcut for the interpreter looking up the value by name. This concept becomes very unclear when dealing with methods. At first, I thought it would work the same way. For example:
var obj = {
fn: function () {
// Do something
return this.value;
},
value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
As it is, this will not work at all. Accessing the method like this removes it from its scope. When it reaches the line this.value, this refers to the window object and this.value will probably be undefined. Instead of directly calling objFn and losing scope, I could pass its scope back into it with objFn.call(obj) but does this perform any better or worse then the original obj.fn()?
I decided to write a script to test this and I got very confusing results. This script makes iterations over several tests which loop through the above function calls many times. The average time taken for each test is output to the body.
An object is created with many simple methods on it. The extra methods are there to determine if the interpreter has to work much harder to locate a specific method.
Test 1 simply calls this.a();
Test 2 creates a local variable a = this.a then calls a.call(this);
Test 3 creates a local variable using YUI's bind function to preserve scope. I commented this out. The extra function calls created by YUI make this way slower.
Tests 4, 5, and 6 are copies of 1, 2, 3 except using z instead of a.
YUI's later function is used to prevent runaway script errors. The timing is done in the actual test methods so the setTimeouts should not effect the results. Each function is called a total of 10000000 times. (Easily configurable if you want to run tests.)
Here's my entire XHTML document I used to test.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr">
<head>
<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var o = {
value: '',
a: function () {
this.value += 'a';
},
b: function () {
this.value += 'b';
},
c: function () {
this.value += 'c';
},
d: function () {
this.value += 'd';
},
e: function () {
this.value += 'e';
},
f: function () {
this.value += 'f';
},
g: function () {
this.value += 'g';
},
h: function () {
this.value += 'h';
},
i: function () {
this.value += 'i';
},
j: function () {
this.value += 'j';
},
k: function () {
this.value += 'k';
},
l: function () {
this.value += 'l';
},
m: function () {
this.value += 'm';
},
n: function () {
this.value += 'n';
},
o: function () {
this.value += 'o';
},
p: function () {
this.value += 'p';
},
q: function () {
this.value += 'q';
},
r: function () {
this.value += 'r';
},
s: function () {
this.value += 's';
},
t: function () {
this.value += 't';
},
u: function () {
this.value += 'u';
},
v: function () {
this.value += 'v';
},
w: function () {
this.value += 'w';
},
x: function () {
this.value += 'x';
},
y: function () {
this.value += 'y';
},
z: function () {
this.value += 'z';
},
reset: function () {
this.value = '';
},
test1: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.a();
}
return new Date().getTime() - time;
},
test2: function (length) {
var a = this.a,
time = new Date().getTime();
while ((length -= 1)) {
a.call(this);
}
return new Date().getTime() - time;
},
test3: function (length) {
var a = Y.bind(this.a, this),
time = new Date().getTime();
while ((length -= 1)) {
a();
}
return new Date().getTime() - time;
},
test4: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.z();
}
return new Date().getTime() - time;
},
test5: function (length) {
var z = this.z,
time = new Date().getTime();
while ((length -= 1)) {
z.call(this);
}
return new Date().getTime() - time;
},
test6: function (length) {
var z = Y.bind(this.z, this),
time = new Date().getTime();
while ((length -= 1)) {
z();
}
return new Date().getTime() - time;
}
},
iterations = 100, iteration = iterations, length = 100000,
t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body');
body.set('innerHTML', '<span>Running ' + iterations + ' Iterations…</span>');
while ((iteration -= 1)) {
Y.later(1, null, function (iteration) {
Y.later(1, null, function () {
o.reset();
t1 += o.test1(length);
});
Y.later(1, null, function () {
o.reset();
t2 += o.test2(length);
});
/*Y.later(1, null, function () {
o.reset();
t3 += o.test3(length);
});*/
Y.later(1, null, function () {
o.reset();
t4 += o.test4(length);
});
Y.later(1, null, function () {
o.reset();
t5 += o.test5(length);
});
/*Y.later(1, null, function () {
o.reset();
t6 += o.test6(length);
});*/
if (iteration === 1) {
Y.later(10, null, function () {
t1 /= iterations;
t2 /= iterations;
//t3 /= iterations;
t4 /= iterations;
t5 /= iterations;
//t6 /= iterations;
//body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>');
body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>');
});
}
}, iteration);
}
});
</script>
</head>
<body>
</body>
</html>
I've run this using Windows 7 in three different browsers. These results are in milliseconds.
Firefox 3.6.8
Test 1: this.a();
9.23
Test 2: a.call(this);
9.67
Test 4: this.z();
9.2
Test 5: z.call(this);
9.61
Chrome 7.0.503.0
Test 1: this.a();
5.25
Test 2: a.call(this);
4.66
Test 4: this.z();
3.71
Test 5: z.call(this);
4.15
Internet Explorer 8
Test 1: this.a();
168.2
Test 2: a.call(this);
197.94
Test 4: this.z();
169.6
Test 5: z.call(this);
199.02
Firefox and Internet Explorer produced results about how I expected. Test 1 and Test 4 are relatively close, Test 2 and Test 5 are relatively close, and Test 2 and Test 5 take longer than Test 1 and Test 4 because there is an extra function call to process.
Chrome I don't understand at all, but it's so much faster, perhaps the tweaking of sub-millisecond performance is unnecessary.
Does anyone have a good explanation of the results? What is the best way to call JavaScript methods multiple times?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
只是理论上的,所以对此持保留态度......
Chrome 的 Javascript 引擎 V8 使用一种称为隐藏类的优化技术。基本上,它构造了隐藏动态 Javascript 对象的静态对象,其中每个属性/方法都映射到固定的内存地址,可以立即引用该地址,而无需昂贵的表查找操作。每次 Javascript 对象添加/删除属性时,都会创建一个新的隐藏类。
我对 Chrome 测试结果的理论是,在自由局部变量中引用该函数会破坏隐藏的类关系。虽然引用局部变量可能也不需要表查找,但现在必须执行额外的步骤来重新分配“this”变量。对于隐藏类上的方法,“this”是一个固定值,因此无需此步骤即可调用它。
再次只是理论上。也许值得测试一下 Chrome 中局部变量引用与 object.member 引用之间的差异,看看后者对性能的影响是否明显小于其他浏览器,大概是因为隐藏类。
Just theorizing, so take this with a grain of salt...
Chrome's Javascript engine, V8, uses an optimization technique called Hidden Classes. Basicly it constructs static objects that shadow dynamic Javascript objects, where each property/method is mapped to a fixed memory address that can be immeditaly referenced with out the need for an expensive table lookup operation. Every time a Javascript object has a property added/removed, a new hidden class is created.
My theory for the results of your test with Chrome, is that referencing the function in a free local variable breaks the hidden class relationship. While referencing local variables probably also do not require a table lookup, an extra step must now be performed in re-assigning the 'this' variable. For a method on a hidden class, 'this' is a fixed value, so it can be invoked without this step.
Again just theorizing. It might be worth a test to benchmark the difference between local variable references vs object.member references in Chrome, to see if the hit to performance for the latter is signficantly less than in other browsers, presumably because of Hidden Classes.
好吧,只要您的网站有 IE8 用户作为访问者,这就完全无关紧要。使用 1 或 3(用户不会看到差异)。
对于“为什么”这个问题,可能没有一个好的答案。当谈到优化时,这些脚本引擎可能会专注于优化他们在现实生活中经常看到的场景,在这些场景中,优化可以被证明是正确的,在哪些地方它会产生影响,并且在某种程度上它会失效最少的测试量。
Well, as long as your website has IE8 users as visitors, this is quite irrelevant. Use 1 or 3 (users wont see a difference).
There is probably not a good answer to the "why" question. When it comes to optimization, these script engines are likely to focus on optimizing scenarios that they see happen a lot in real life, where the optimization can be proven to work correct, and where it makes a difference, and in a way that it invalidates the least amount of testing.
一般来说,如果您不知道它会花费很多时间,那么您所做的任何事情都不太可能产生影响。 (“很多”是指很大的百分比。)
这里有一个简单的方法可以找出哪些代码占用了很多时间。
Just as a general point, anything you do is unlikely to make a difference if you don't know it is responsible for much time. (By "much" I mean a significant percentage.)
Here's an easy way to find out which code is responsible for much time.