javascript オブジェクトをループさせる処理で「forEach」と「map」と「for文」と「外部ライブラリ」のパフォーマンスを計測する

javascript オブジェクトをループさせる処理で「forEach」と「map」と「for文」と「外部ライブラリ」のパフォーマンスを計測する

javascriptで、「forEach」と「map」と「for文」と「外部ライブラリ」で、オブジェクトをループさせる処理を行った時のパフォーマンスを計測するサンプルコードを記述してます。

環境

  • OS windows11 pro 64bit
  • Apache 2.4.43
  • ブラウザ chrome 103.0.5060.114

パフォーマンス計測

「performance.now」を使用して、「forEach」と「map」と「for文」と「外部ライブラリ」を使用して、オブジェクトを作成してループさせる処理を100万回実行し、パフォーマンスを計測するサンプルコードとなります。
※for-inや外部ライブラリはプロトタイプまでループするものがあるため、プロトタイプを追加して、それを除外するようにして比較してます。

<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"
  integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ=="
  crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

<script>

  // 実行回数
  const times = 1_000_000;

  // 空白を埋めるだけの関数
  function spacePadding(val, n = 8) {
    for (; val.length < n; val += ' ');
    return val;
  }

  // 計測結果を表示
  const benchmark = (name, start, end) => {
    let report = (end - start).toPrecision(3);
    // 表示を見やすくするため関数名に空白を埋める
    name = spacePadding(name)
    console.log(`実行回数:${times}回 関数名:${name} 実行時間:${report}(ms)`);
  }

  const obj = {
    "a": "1",
    "b": "2",
    "c": "3",
    "d": "4",
    "e": "5"
  }

  let obj2 = {
    "f": "6"
  }

  // Prototypeを追加(for-inや外部ライブラリはプロトタイプまでループするものがあるため)
  obj.__proto__ = obj2;

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {
    Object.keys(obj).forEach((key) => (`key : ${key} value : ${obj[key]}`));
  }

  end = performance.now();

  benchmark('Object.keys(obj).forEach', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {
    Object.entries(obj).forEach(([key, value], index) => (`key : ${key} value : ${value}`))
  }

  end = performance.now();

  benchmark('Object.entries(obj).forEach', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    Object.entries(obj).map(([key, value]) => (`key : ${key} value : ${value}`))

  }

  end = performance.now();

  benchmark('Object.entries(obj).map', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    for (let key in obj) { if (obj.hasOwnProperty(key)) { (`key : ${key} value : ${obj[key]}`) } }

  }

  end = performance.now();

  benchmark('for-in', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    for (let key of Object.keys(obj)) { (`key : ${key} value : ${obj[key]}`) }

  }

  end = performance.now();

  benchmark('for-of Object.keys', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    for (let [key, value] of Object.entries(obj)) { (`key : ${key} value : ${obj[key]}`) }

  }

  end = performance.now();

  benchmark('for-of Object.entries', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    $.each(obj, (key, value) => { if (obj.hasOwnProperty(key)) { (`key : ${key} value : ${value}`) } })

  }

  end = performance.now();

  benchmark('jQuery', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    _.each(obj, (value, key) => (`key : ${key} value : ${value}`))

  }

  end = performance.now();

  benchmark('underscore', start, end);

  // 計測
  start = performance.now();

  for (let i = 0; i < times; ++i) {

    _.forIn(obj, (value, key) => { if (obj.hasOwnProperty(key)) { (`key : ${key} value : ${value}`) } })

  }

  end = performance.now();

  benchmark('lodash', start, end);

</script>

実行結果(chrome 103.0.5060.114)

<1回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:199(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:198(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:248(ms)
実行回数:1000000回 関数名:for-in   実行時間:414(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:175(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:237(ms)
実行回数:1000000回 関数名:jQuery   実行時間:657(ms)
実行回数:1000000回 関数名:underscore 実行時間:263(ms)
実行回数:1000000回 関数名:lodash   実行時間:553(ms)

<2回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:220(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:218(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:220(ms)
実行回数:1000000回 関数名:for-in   実行時間:419(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:172(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:233(ms)
実行回数:1000000回 関数名:jQuery   実行時間:657(ms)
実行回数:1000000回 関数名:underscore 実行時間:272(ms)
実行回数:1000000回 関数名:lodash   実行時間:549(ms)

<3回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:213(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:187(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:223(ms)
実行回数:1000000回 関数名:for-in   実行時間:423(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:174(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:246(ms)
実行回数:1000000回 関数名:jQuery   実行時間:643(ms)
実行回数:1000000回 関数名:underscore 実行時間:264(ms)
実行回数:1000000回 関数名:lodash   実行時間:561(ms)

「for-of」で「Object.keys」を使用するのが一番いいという結果になりました。

firefox102の場合は「Object.keys(obj).forEach」が良さそうです。

<1回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:165(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:742(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:787(ms)
実行回数:1000000回 関数名:for-in   実行時間:168(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:203(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:556(ms)
実行回数:1000000回 関数名:jQuery   実行時間:334(ms)
実行回数:1000000回 関数名:underscore 実行時間:538(ms)
実行回数:1000000回 関数名:lodash   実行時間:413(ms)

<2回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:160(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:740(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:775(ms)
実行回数:1000000回 関数名:for-in   実行時間:168(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:202(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:549(ms)
実行回数:1000000回 関数名:jQuery   実行時間:330(ms)
実行回数:1000000回 関数名:underscore 実行時間:526(ms)
実行回数:1000000回 関数名:lodash   実行時間:399(ms)

<3回目>
実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:188(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:869(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:775(ms)
実行回数:1000000回 関数名:for-in   実行時間:172(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:205(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:551(ms)
実行回数:1000000回 関数名:jQuery   実行時間:331(ms)
実行回数:1000000回 関数名:underscore 実行時間:534(ms)
実行回数:1000000回 関数名:lodash   実行時間:396(ms)

safari15.5の場合は「Object.keys(obj).forEach」が良さそうです。

実行回数:1000000回 関数名:Object.keys(obj).forEach 実行時間:282(ms)
実行回数:1000000回 関数名:Object.entries(obj).forEach 実行時間:1.16e+3(ms)
実行回数:1000000回 関数名:Object.entries(obj).map 実行時間:1.18e+3(ms)
実行回数:1000000回 関数名:for-in   実行時間:534(ms)
実行回数:1000000回 関数名:for-of Object.keys 実行時間:927(ms)
実行回数:1000000回 関数名:for-of Object.entries 実行時間:2.25e+3(ms)
実行回数:1000000回 関数名:jQuery   実行時間:648(ms)
実行回数:1000000回 関数名:underscore 実行時間:743(ms)
実行回数:1000000回 関数名:lodash   実行時間:818(ms)