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

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

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

## TL;DR
- 速度重視ならアロー関数は使うな
- for文で配列の長さを使うなら変数に保存しろ
- さほど大きくない数字の配列を扱うならUint8ArrayやInt16Array等を使え、ただし範囲はよく調べろ
- thisやクラスはスコープ内変数より遅い
## 計測方法
```js
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)
内容のない関数二つの速度を比較します。
```js
check("arrow noop",()=>{})
check("funct noop",function(){})
```
### 予想
thisの余計な計算がないからアロー関数の方が速そう
### 結果
```
arrow noop:0.0005066499999999692
funct noop:0.0001306200000000061
```
通常の関数の方が速かった...
### 考察
アロー関数はスコープをたどらないといけないため遅くなった？
## BigIntでループ
bigintとnumberで1万回ループします
```js
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が普通に遅い
## 配列の長さのキャッシュ
長さを保存してからループします
```js
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万回のループをします
```js
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万回ループします
```js
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を代入します
```js
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文どちらが速いか
```js
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
スコープ内の変数によるループとクラス内のループで比較します。
ついでにプライベート変数も比較します。
```js
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、どちらが速いかこれでもう一度確かめます。以下はここのすべてのコード比較コードです
```js
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だと最適化方法が違うのかな？