0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptでコードの最適化時その他の速度比較してみた

Last updated at Posted at 2024-12-26

今回は最適化の効果等について検証します。

世の中にはコードが短ければ速くなると思っている人がいますがJavaScriptはJITコンパイラが使われていることがほとんどなので計算段階での計算速度への影響はありません。

TL;DR

  • 速度重視ならアロー関数は使うな
  • for文で配列の長さを使うなら変数に保存しろ
  • さほど大きくない数字の配列を扱うならUint8ArrayやInt16Array等を使え、ただし範囲はよく調べろ
  • thisやクラスはスコープ内変数より遅い

計測方法

const checkcount = 10000;
const check = (n,fn) => {
    const c = () => {
        const start = performance.now();
        fn();
        const end = performance.now();
        return end - start;
    }
    const res = [];
    for(let i = 0;i < checkcount;i++)
        res.push(c())
    console.log(n+":"+res.reduce((p,c)=>p+=c)/checkcount)
}

performance.now()で時間を取得して1万回行った平均を求めます。

環境

Intel(R) Core(TM) -5-7200U CPU @ 2.50GHz
Intel(R) HD Graphics 620
Microsoft Windows Pro 10 バージョン22H2 OSビルド19045.5247
nodejs v20.12.2
bun 1.1.38
Visual studio code
powershell

YouTubeとか見ながらしてたので多少左右します

1.匿名関数(noop)

内容のない関数二つの速度を比較します。

check("arrow noop",()=>{})
check("funct noop",function(){})

予想

thisの余計な計算がないからアロー関数の方が速そう

結果

arrow noop:0.0005066499999999692
funct noop:0.0001306200000000061

通常の関数の方が速かった...

考察

アロー関数はスコープをたどらないといけないため遅くなった?

BigIntでループ

bigintとnumberで1万回ループします

check("number loop",()=>{
    for(let i = 0; i < 10000;i++);
})
check("bigint loop",()=>{
    for(let i = 0n;i < 10000n;i++);
})

予想

numberと違ってBigIntは小数点がないから速そう

結果

number loop:0.006236490000000312
bigint loop:0.4420627400000003

BigIntのボロ負け

考察

BigIntはJIT内でループの最適化がされなかった?
それかBigIntが普通に遅い

配列の長さのキャッシュ

長さを保存してからループします

check("len cached",()=>{
    const arr = new Array(10000);
    const len = arr.length
    for(let i = 0;i < len;i++);
})
check("len nocache",()=>{
    const arr = new Array(10000);
    for(let i = 0;i < arr.length;i++);
})

予想

どっかのサイトにこれで速くなるとあったし理屈が通ってるので速くなるね、言うまでもない。

結果

len cached:0.00830133999999707
len nocache:0.022141320000002726

速い!

考察

普通にlengthのゲッター処理があるから時間がかかる?

evalを使う

evalと通常表記で1万回のループをします

check("eval",()=>{
    eval("for(let Z=0;Z<10000;Z++);")
})
check("native",()=>{
    for(let i=0;i<10000;i++);
})

予想

evalごとにJITコンパイラかインタプリタが動くのでevalは遅いね、言うまでもない。

結果

eval:0.007514940000000057
native:0.004491899999999981

evalの方がちょっと遅い

考察

処理の使いまわしをしている?

var vs. let

var宣言とlet宣言で10万回ループします

check("var",()=>{
    for(var i=0;i<100000;i++);
})
check("let",()=>{
    for(let i=0;i<100000;i++);
})

予想

印象的にvarが遅そう

結果

var:0.0510033400000004
let:0.03656134999999887

何回か計測したけどletの方が遅いこともあった

考察

宣言方法に大差無し!

Uint8ArrayとArrayで10万回の代入

Uint8ArrayとArrayに1を代入します

check("uint8loop",()=>{
    const arr = new Uint8Array(100000);
    for(let i = 0;i < arr.length;i++) arr[i] = 1;
})
check("arrayloop",()=>{
    const arr = new Array(100000);
    for(let i = 0;i < arr.length;i++) arr[i] = 1;
})

予想

Uint8Arrayの方が機械に近くて速そう

結果

uint8loop:0.2944365700000023
arrayloop:0.8646147300000013

目に見えてUint8Arrayが速い!

考察

Uint8Arrayは型が確定しているか、機械に近いか。

三項演算子

三項演算子とIF文どちらが速いか

check("?:",()=>{
    const arr = [];
    for(let i = 0;i < 10000;i++) i % 2 ? arr.push(5) : arr.push(2);
})
check("if",()=>{
    const arr = [];
    for(let i = 0;i < 10000;i++)
        if(i % 2) arr.push(5)
            else arr.push(2)
})

予想

三項演算子の方が速そう

結果

?::0.056880590000000064
if:0.05327013000000045

微々たる差だがif文の方が速い!

考察

if文の方が機械に近い?

スコープとthis

スコープ内の変数によるループとクラス内のループで比較します。
ついでにプライベート変数も比較します。

check("scope",()=>{
    function loopFactory(){
        let i = 0;
        return function(){
            for(i = 0;i < 10000;i++);
        }
    }
    const loop = loopFactory();
    loop();
})
check("class",()=>{
    class Loop{
        i;
        constructor(){
            this.i = 0;
        }
        loop(){
            for(this.i = 0;this.i < 10000;this.i++);
        }
    }
    const loop = new Loop();
    loop.loop();
})
check("classP",()=>{
    class Loop{
        #i;
        constructor(){
            this.#i = 0;
        }
        loop(){
            for(this.#i = 0;this.#i < 10000;this.#i++);
        }
    }
    const loop = new Loop();
    loop.loop();
})

考察

thisを介さないといけないからクラスは遅いけどプライベート変数は外部に表示しないから比較的速い?

結果

scope:0.036733379999999934
class:0.2731350399999993
classP:0.45610956999999763

圧倒的スコープ

考察

thisを介すのはやっぱり遅いよね...
プライベート変数は特別な処理をするのかな?

node vs. bun

nodeとbun、どちらが速いかこれでもう一度確かめます。以下はここのすべてのコード比較コードです

const checkcount = 10000;
const check = (n,fn) => {
    const c = () => {
        const start = performance.now();
        fn();
        const end = performance.now();
        return end - start;
    }
    const res = [];
    for(let i = 0;i < checkcount;i++)
        res.push(c())
    console.log(n+":"+res.reduce((p,c)=>p+=c)/checkcount)
}
check("arrow noop",()=>{})
check("funct noop",function(){})
console.log("")

check("number loop",()=>{
    for(let i = 0; i < 10000;i++);
})
check("bigint loop",()=>{
    for(let i = 0n;i < 10000n;i++);
})
console.log("")

check("len cached",()=>{
    const arr = new Array(10000);
    const len = arr.length
    for(let i = 0;i < len;i++);
})
check("len nocache",()=>{
    const arr = new Array(10000);
    for(let i = 0;i < arr.length;i++);
})
console.log("")

check("eval",()=>{
    eval("for(let Z=0;Z<10000;Z++);")
})
check("native",()=>{
    for(let i=0;i<10000;i++);
})
console.log("")

check("var",()=>{
    for(var i=0;i<100000;i++);
})
check("let",()=>{
    for(let i=0;i<100000;i++);
})
console.log("")

check("uint8loop",()=>{
    const arr = new Uint8Array(100000);
    for(let i = 0;i < arr.length;i++) arr[i] = 1;
})
check("arrayloop",()=>{
    const arr = new Array(100000);
    for(let i = 0;i < arr.length;i++) arr[i] = 1;
})
console.log("")

check("?:",()=>{
    const arr = [];
    for(let i = 0;i < 10000;i++) i % 2 ? arr.push(5) : arr.push(2);
})
check("if",()=>{
    const arr = [];
    for(let i = 0;i < 10000;i++)
        if(i % 2) arr.push(5)
            else arr.push(2)
})

結果

頑張ってテーブル化しました。きつい。

タイトル nodejs bun
arrow noop 0.0002971100000000263 0.00023385000000004084
funct noop 0.00017492000000002435 0.00007084999999999298
number loop 0.005526530000000038 0.011351059999999987
bigint loop 0.4051602199999979 0.7218548800000042
len cached 0.007827849999996852 0.06264597000000012
len nocache 0.013203849999999875 0.057115710000000125
eval 0.004763229999995383 0.022002820000001703
native 0.004406499999999687 0.013971630000003097
var 0.03667173999999968 0.11217761000001501
let 0.037452690000006714 0.11275581000001311
uint8loop 0.2656331500000025 0.17104559999998656
arrayloop 0.8199938500000077 0.45781148000002103
?: 0.05362607000000017 0.10521647000000026
if 0.052280720000000044 0.07787996000000007
scope 0.036733379999999934 0.02870950999999985
class 0.2731350399999993 0.019864589999999804
classP 0.45610956999999763 1.3481609800000118

やっぱりbunは速い!と思ったけど所々遅い部分も。
集計ミスもあるかもだけどV8製nodejsとwebkit製bunだと最適化方法が違うのかな?

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?