JavaScript 配列への要素追加方法による速度検証

2021/09/12

JavaScript で Logger を用意しようかと思ったんですが、ログを配列で管理する場合、JavaScript だと初回で配列の長さを指定する場合と、毎回配列にpushする場合でどのくらい処理速度に差が出るのか気になったので検証してみました。

以下が検証コードです。100万回配列に要素を追加する時間を計測しました。

// 毎回 push するコード
{
    let average = 0;
    const sampleCount = 100;
    for (let j = 0; j < sampleCount; ++j) {
        let array = [];
        let start = performance.now();
        for (let i = 0; i < 1000000; ++i) {
            array.push(i);
        }
        let end = performance.now();
        let diff = end - start;
        average += diff;
    }
    average = average / sampleCount;
    console.log(`${average} ms`);
}

// 初回に配列の長さを指定するコード
{
    let average = 0;
    const sampleCount = 100;
    for (let j = 0; j < sampleCount; ++j) {
        let array = new Array(1000000);
        let start = performance.now();
        for (let i = 0; i < 1000000; ++i) {
            array[i] = i;
        }
        let end = performance.now();
        let diff = end - start;
        average += diff;
    }
    average = average / sampleCount;
    console.log(`${average} ms`);
}

検証ブラウザは現時点の最新バージョンであるChrome 93.0、Firefox 92.0 の2つです。

Chromeでの結果は前者が 23 ms 程、後者が 1.2 ms 程、Firefoxでは前者が18ms程、後者は17ms程でした。

Chrome の方では 100万回だと結構差が出るので、push 時の配列メモリ拡張量が length + 1、もしくは少なめに設定されているようです。Firefox の方はほとんど差が見られないので、push 時の配列メモリ拡張量がよくある length の2乗とかみたいに大きく拡張されていそうです。詳しくは調べていません。

少なくとも Chrome は利用するメインのブラウザとして考えているので、事前に最大配列数で配列を初期化しておく実装にしておく価値はありそうです。

次に私の本目的にもっと近い形での速度を調べるために、簡易的な Logger を作って、そちらでも速度比較してみました。

class Logger1 {
    constructor() {
        this.logs = [];
    }

    writeLog(log) {
        this.logs.push(log);
    }
}

class Logger2 {
    constructor(maxLogCount) {
        this.logs = new Array(maxLogCount);
        this.logCount = 0;
    }

    writeLog(log) {
        this.logs[this.logCount] = log;
        ++this.logCount;
    }
}


const MaxLogCount = 100000;
const Increment = 2000;
for (let maxLogCount = Increment; maxLogCount <= MaxLogCount; maxLogCount += Increment) {
    {
        let average = 0;
        const sampleCount = 100;
        for (let j = 0; j < sampleCount; ++j) {
            let logger = new Logger1();
            let start = performance.now();
            for (let i = 0; i < maxLogCount; ++i) {
                logger.writeLog("hoge");
            }
            let end = performance.now();
            let diff = end - start;
            average += diff;
        }
        average = average / sampleCount;
        console.log(`${maxLogCount}\t${average}`);
    }
}
for (let maxLogCount = Increment; maxLogCount <= MaxLogCount; maxLogCount += Increment) {
    {
        let average = 0;
        const sampleCount = 100;
        for (let j = 0; j < sampleCount; ++j) {
            let logger = new Logger2(maxLogCount);
            let start = performance.now();
            for (let i = 0; i < maxLogCount; ++i) {
                logger.writeLog("hoge");
            }
            let end = performance.now();
            let diff = end - start;
            average += diff;
        }
        average = average / sampleCount;
        console.log(`${maxLogCount}\t${average}`);
    }
}

Chrome での結果は以下のような感じになりました。青が Logger1 で、オレンジが Logger2 の処理時間です。

多少誤差でガタガタしてますが、ログ数に応じて線形で処理時間が増加する結果になりました。ログ数が1000とかだとあまり違いはないですが、ログ数が増えるにつれて差が大きくなるようです。

一応 Firefox でも確認しましたが、Firefox では最初の検証通り、両者に差がない結果になりました。グラフは割愛します。

正直、実際の利用用途においてログ数が1000を超えるかというと微妙なところですが、配列の初期化コストを特に気にするような用途ではないので、Chrome でも Firefox でも特にデメリットのない Logger2 の方の実装をベースに目的の Logger を作ろうと思います。


  このエントリーをはてなブックマークに追加  

<<「Web 開発」の記事一覧に戻る

関連記事