JavaScriptのスプレッド構文は遅い

2022/12/15に公開

こういうデータがあったとして、一定条件を満たすエンティティとそのIDの配列を作りたい場合、どうやって書きますかというお話

import { randomUUID } from "crypto";

type Entity = { id: string, value: number };

const entities: Entity[] = [];
for (let i = 0; i < 100000; i++) {
    entities.push({ id: randomUUID(), value: Math.random() });
}

type Result = [string[], Entity[]];

思いつくパターン

パターン1:可読性の高い書き方

ループは無駄に二回まわってしまうけど、可読性が高い

const filter1 = (): Result => {
    const filteredEntities = entities.filter((entity) => entity.value > 0.5);
    const filteredIds = filteredEntities.map(({ id }) => id);

    return [filteredIds, filteredEntities];
};

パターン2:ループ数を減らす

ループ数を減らすためにreduceを使う

const filter2 = (): Result => entities.reduce<Result>((result, entity) => {
    if (entity.value <= 0.5) return result;

    return [
        [...result[0], entity.id],
        [...result[1], entity],
    ];
}, [[], []]);

パターン3:愚直に

愚直のfor文で書く

const filter3 = (): Result => {
    const result: Result = [[], []];

    for (const entity of entities) {
        if (entity.value <= 0.5) continue;
        result[0].push(entity.id);
        result[1].push(entity);
    }

    return result;
};

計測

const measure = (label: string, handler: () => void) => {
    performance.mark("start");
    handler();
    performance.mark("finish");
    performance.measure(label, "start", "finish");
};

measure("filter1", filter1);
measure("filter2", filter2);
measure("filter3", filter3);

console.info(performance.getEntriesByType("measure").map(({name, duration}) => `${name} : ${Math.floor(duration)}ms`).join("\n"));

結果

filter1 : 4ms
filter2 : 7698ms
filter3 : 3ms

要素数を増やして1と3だけ計測

filter1 : 295ms
filter3 : 289ms

結論

  • スプレッド構文は遅い
    • 純粋関数を書きたい時はつい使っちゃうけど、まぁそりゃそう
  • ループ数が増えても処理の量が変わらなければ速度への影響はあっても誤差範囲
    • そんなことより可読性を高めていこう

Discussion