apply と ...
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ apply のほうが速い
ES2015 で ... 演算子で関数呼び出しの時に apply のように配列を展開できて パフォーマンスの悪い apply を使わなくて済む と聞いた覚えがあります
使えるようになった当時では JavaScript エンジンの実装面のチューニングもそこまでされていなくて早くないかもしれません
でも もうそろそろ 次の ES2017 が 2 ヶ月もすれば出る頃で ES2015 が使えるようになってけっこう時間も経ちましたしどっちが速いのか調べてみようかと思いました
手動での展開は可変個数だと無理があるので 今回は3つの要素の配列にしてます
それぞれ 3 の数字が入った配列が10万個ある配列を作って足し算するものです
4 種類の呼び出し方はこうなります
くわしくは全体を
さて 結果は
[Chrome]
[Firefox]
いつもどおり Chrome は for 文の 2 回目以降がキャッシュのためか高速化してます
一番早いのは call で一番遅いのが bind + ... と言う結果でした
bind してる 2 つは遅いです
新しく関数を作ってるので仕方ないともいえます
call と apply だと call のほうが速く 速度を求めるときで配列の個数が固定なら手動で要素を分けて call にしたほうが良さそうです
call と apply のように bind した関数に手動で要素を分けて呼び出すのと ... 演算子で自動で展開させるのだと ... 演算子のほうが遅くなってます
結果 apply と bind + ... では bind で関数作る分 apply のほうが速いと言う結果でした
この 2 種類
bind と bind + ... と同じ結果な気がします
[Chrome]
[Firefox]
同じようなものですね
通常の呼び出しにしたほうが速いです
Firefox の ... の遅さはちょっと遅すぎかも
30 倍くらいって
配列の要素が 2 つだったり 50 だったり可変だと if 文で全部個数分書かないといけなくなります
そのための apply なので bind したものと比較というより
この 2 つを比べるのが重要な気がしました
(というのにブログ書き始めてから気づいたので ここは計測マシンが異なります)
[Chrome]
[Firefox]
Chrome はこれまでのほど差はないですが apply のほうが速いです
1.2倍~1.5倍くらい
Firefox は ... が異常に遅いので 10 ~ 20 倍くらいの差
まだまだ改善されていきそうではありますが しばらくは速度面を気にするなら call/apply のほうがよさそうです
使えるようになった当時では JavaScript エンジンの実装面のチューニングもそこまでされていなくて早くないかもしれません
でも もうそろそろ 次の ES2017 が 2 ヶ月もすれば出る頃で ES2015 が使えるようになってけっこう時間も経ちましたしどっちが速いのか調べてみようかと思いました
apply と bind + ...
比較するのは 4 つ- bind して ... で関数実行
- apply で関数実行
- call で配列を手動で展開して実行
- bind して配列を手動で展開して実行
手動での展開は可変個数だと無理があるので 今回は3つの要素の配列にしてます
それぞれ 3 の数字が入った配列が10万個ある配列を作って足し算するものです
4 種類の呼び出し方はこうなります
fn.bind(obj)(...a[i])
fn.apply(obj, a[i])
fn.call(obj, a[i][0], a[i][1], a[i][2])
fn.bind(obj)(a[i][0], a[i][1], a[i][2])
fn.apply(obj, a[i])
fn.call(obj, a[i][0], a[i][1], a[i][2])
fn.bind(obj)(a[i][0], a[i][1], a[i][2])
くわしくは全体を
var a = []
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var fn = function(x, y, z){ this.sum += x + y + z }
for(var i=0;i<5;i++){
t1("bind...")
t2("apply")
t3("call")
t4("bind")
}
function t1(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.bind(obj)(...a[i])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t2(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.apply(obj, a[i])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t3(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.call(obj, a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t4(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.bind(obj)(a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
console.log(obj.sum)
}
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var fn = function(x, y, z){ this.sum += x + y + z }
for(var i=0;i<5;i++){
t1("bind...")
t2("apply")
t3("call")
t4("bind")
}
function t1(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.bind(obj)(...a[i])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t2(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.apply(obj, a[i])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t3(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.call(obj, a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
console.log(obj.sum)
}
function t4(name){
var obj = {sum: 0}
console.time(name)
for(var i=0;i<a.length;i++){
fn.bind(obj)(a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
console.log(obj.sum)
}
さて 結果は
[Chrome]
bind...: 13.3ms
apply: 4.84ms
call: 2.67ms
bind: 8.76ms
bind...: 7.96ms
apply: 4.11ms
call: 2.53ms
bind: 5.80ms
bind...: 6.09ms
apply: 2.47ms
call: 0.676ms
bind: 3.88ms
bind...: 6.81ms
apply: 2.24ms
call: 0.460ms
bind: 2.76ms
bind...: 6.34ms
apply: 2.43ms
call: 0.509ms
bind: 3.44ms
apply: 4.84ms
call: 2.67ms
bind: 8.76ms
bind...: 7.96ms
apply: 4.11ms
call: 2.53ms
bind: 5.80ms
bind...: 6.09ms
apply: 2.47ms
call: 0.676ms
bind: 3.88ms
bind...: 6.81ms
apply: 2.24ms
call: 0.460ms
bind: 2.76ms
bind...: 6.34ms
apply: 2.43ms
call: 0.509ms
bind: 3.44ms
[Firefox]
bind...: タイマー開始
bind...: 110.84ms
apply: タイマー開始
apply: 3.86ms
call: タイマー開始
call: 3.26ms
bind: タイマー開始
bind: 26.78ms
bind...: タイマー開始
bind...: 104.48ms
apply: タイマー開始
apply: 3.54ms
call: タイマー開始
call: 2.56ms
bind: タイマー開始
bind: 28.71ms
bind...: タイマー開始
bind...: 104.53ms
apply: タイマー開始
apply: 3ms
call: タイマー開始
call: 1.63ms
bind: タイマー開始
bind: 26.91ms
bind...: タイマー開始
bind...: 105.31ms
apply: タイマー開始
apply: 3.63ms
call: タイマー開始
call: 1.62ms
bind: タイマー開始
bind: 25.08ms
bind...: タイマー開始
bind...: 103.53ms
apply: タイマー開始
apply: 2.96ms
call: タイマー開始
call: 1.66ms
bind: タイマー開始
bind: 23.42ms
bind...: 110.84ms
apply: タイマー開始
apply: 3.86ms
call: タイマー開始
call: 3.26ms
bind: タイマー開始
bind: 26.78ms
bind...: タイマー開始
bind...: 104.48ms
apply: タイマー開始
apply: 3.54ms
call: タイマー開始
call: 2.56ms
bind: タイマー開始
bind: 28.71ms
bind...: タイマー開始
bind...: 104.53ms
apply: タイマー開始
apply: 3ms
call: タイマー開始
call: 1.63ms
bind: タイマー開始
bind: 26.91ms
bind...: タイマー開始
bind...: 105.31ms
apply: タイマー開始
apply: 3.63ms
call: タイマー開始
call: 1.62ms
bind: タイマー開始
bind: 25.08ms
bind...: タイマー開始
bind...: 103.53ms
apply: タイマー開始
apply: 2.96ms
call: タイマー開始
call: 1.66ms
bind: タイマー開始
bind: 23.42ms
いつもどおり Chrome は for 文の 2 回目以降がキャッシュのためか高速化してます
一番早いのは call で一番遅いのが bind + ... と言う結果でした
bind してる 2 つは遅いです
新しく関数を作ってるので仕方ないともいえます
call と apply だと call のほうが速く 速度を求めるときで配列の個数が固定なら手動で要素を分けて call にしたほうが良さそうです
call と apply のように bind した関数に手動で要素を分けて呼び出すのと ... 演算子で自動で展開させるのだと ... 演算子のほうが遅くなってます
結果 apply と bind + ... では bind で関数作る分 apply のほうが速いと言う結果でした
普通の関数呼び出しと ...
this を bind することってそこまで多くもないので 通常の関数呼び出しもやってみますsum(a[i][0], a[i][1], a[i][2])
sum(...a[i])
sum(...a[i])
この 2 種類
bind と bind + ... と同じ結果な気がします
var a = []
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var sum = function(a, b, c){ return a + b + c }
for(var i=0;i<5;i++){
t5("standard")
t6("...")
}
function t5(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
}
function t6(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(...a[i])
}
console.timeEnd(name)
}
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var sum = function(a, b, c){ return a + b + c }
for(var i=0;i<5;i++){
t5("standard")
t6("...")
}
function t5(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(a[i][0], a[i][1], a[i][2])
}
console.timeEnd(name)
}
function t6(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(...a[i])
}
console.timeEnd(name)
}
[Chrome]
standard: 2.13ms
...: 5.03ms
standard: 0.402ms
...: 4.34ms
standard: 0.465ms
...: 3.93ms
standard: 0.400ms
...: 3.84ms
standard: 0.399ms
...: 3.91ms
...: 5.03ms
standard: 0.402ms
...: 4.34ms
standard: 0.465ms
...: 3.93ms
standard: 0.400ms
...: 3.84ms
standard: 0.399ms
...: 3.91ms
[Firefox]
standard: タイマー開始
standard: 2.77ms
...: タイマー開始
...: 58.6ms
standard: タイマー開始
standard: 2.09ms
...: タイマー開始
...: 56.62ms
standard: タイマー開始
standard: 1.77ms
...: タイマー開始
...: 56.33ms
standard: タイマー開始
standard: 1.54ms
...: タイマー開始
...: 55.72ms
standard: タイマー開始
standard: 1.51ms
...: タイマー開始
...: 56.36ms
standard: 2.77ms
...: タイマー開始
...: 58.6ms
standard: タイマー開始
standard: 2.09ms
...: タイマー開始
...: 56.62ms
standard: タイマー開始
standard: 1.77ms
...: タイマー開始
...: 56.33ms
standard: タイマー開始
standard: 1.54ms
...: タイマー開始
...: 55.72ms
standard: タイマー開始
standard: 1.51ms
...: タイマー開始
...: 56.36ms
同じようなものですね
通常の呼び出しにしたほうが速いです
Firefox の ... の遅さはちょっと遅すぎかも
30 倍くらいって
apply と ...
考えてみると通常呼び出しと ... って同じことをしてるわけですが 通常呼び出しにできない場合もありますよね配列の要素が 2 つだったり 50 だったり可変だと if 文で全部個数分書かないといけなくなります
そのための apply なので bind したものと比較というより
sum.apply(null, a[i])
sum(...a[i])
sum(...a[i])
この 2 つを比べるのが重要な気がしました
(というのにブログ書き始めてから気づいたので ここは計測マシンが異なります)
var a = []
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var sum = function(a, b, c){ return a + b + c }
for(var i=0;i<5;i++){
t7("apply")
t8("...")
}
function t7(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum.apply(null, a[i])
}
console.timeEnd(name)
}
function t8(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(...a[i])
}
console.timeEnd(name)
}
for(var i=0;i<100000;i++){
var k = i * 3
a.push([k, k+1, k+2])
}
var sum = function(a, b, c){ return a + b + c }
for(var i=0;i<5;i++){
t7("apply")
t8("...")
}
function t7(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum.apply(null, a[i])
}
console.timeEnd(name)
}
function t8(name){
var total = 0
console.time(name)
for(var i=0;i<a.length;i++){
total += sum(...a[i])
}
console.timeEnd(name)
}
[Chrome]
apply: 5.71ms
...: 7.30ms
apply: 2.29ms
...: 3.75ms
apply: 2.41ms
...: 4.17ms
apply: 2.70ms
...: 3.89ms
apply: 2.17ms
...: 4.03ms
...: 7.30ms
apply: 2.29ms
...: 3.75ms
apply: 2.41ms
...: 4.17ms
apply: 2.70ms
...: 3.89ms
apply: 2.17ms
...: 4.03ms
[Firefox]
apply: タイマー開始
apply: 5.81ms
...: タイマー開始
...: 44.53ms
apply: タイマー開始
apply: 2.66ms
...: タイマー開始
...: 41.42ms
apply: タイマー開始
apply: 2.34ms
...: タイマー開始
...: 41.23ms
apply: タイマー開始
apply: 2.63ms
...: タイマー開始
...: 41.96ms
apply: タイマー開始
apply: 2.41ms
...: タイマー開始
...: 41.32ms
apply: 5.81ms
...: タイマー開始
...: 44.53ms
apply: タイマー開始
apply: 2.66ms
...: タイマー開始
...: 41.42ms
apply: タイマー開始
apply: 2.34ms
...: タイマー開始
...: 41.23ms
apply: タイマー開始
apply: 2.63ms
...: タイマー開始
...: 41.96ms
apply: タイマー開始
apply: 2.41ms
...: タイマー開始
...: 41.32ms
Chrome はこれまでのほど差はないですが apply のほうが速いです
1.2倍~1.5倍くらい
Firefox は ... が異常に遅いので 10 ~ 20 倍くらいの差
まだまだ改善されていきそうではありますが しばらくは速度面を気にするなら call/apply のほうがよさそうです