Skip to content

聽說 forEach 比 for 迴圈慢?

for-each-and-for-loop

常聽到別人說:某些情況下 forEachfor 迴圈還慢。

babylon.js 的 Particle System 文件中,更新大量粒子的迴圈預設也都是使用 for 迴圈。

最近酷酷的元件做了一個下雪背景,粒子數量與計算量相對得多。

剛好藉這個機會來實測看看不同迴圈的實際效果。ԅ(´∀` ԅ)

TIP

以下實驗基於 Google Chrome v131.0.6778.110

超不嚴謹,僅針對此元件情境,請不要當作正式結論。( ゚∀。)

實測

基於原本的程式碼,加上 performance.now 計算執行時間,且每運行 100 次再輸出一次,避免 devtool 炸掉。

ts
let count = 0
particleSystem.updateFunction = function (particles) {
  const startTime = performance.now()

  // 計算 staticMap 與粒子是否有碰撞
  for (const id in staticMap) {
    // ...
  }

  const endTime = performance.now()

  if (count % 100 === 0) {
    console.log({
      particles: particles.length,
      time: endTime - startTime,
    })
  }
  count++
}

for

text
{particles: 1159, time: 0.5}
{particles: 1185, time: 1}
{particles: 1170, time: 0.5999999046325684}
{particles: 1195, time: 1}
{particles: 1200, time: 1.2000000476837158}
{particles: 1194, time: 0.7000000476837158}
{particles: 1201, time: 0.6999999284744263}
{particles: 1182, time: 1.1999999284744263}

可以看到原本粒子數量最多大約在 1200 左右,執行時間在 0.5 ~ 1.2 ms 之間。

forEach

現在來看看 forEach 的執行時間。

TIP

staticMap 的 for 部分維持不變,因為這次只比較矩陣 forEachfor 的差異。

ts
particleSystem.updateFunction = function (particles) {
  const startTime = performance.now()
  // 計算 staticMap 與粒子是否有碰撞
  for (const id in staticMap) {
    const bounding = staticMap[id]
    if (!bounding)
      continue

    /** 依照文件作法更新粒子
     *
     * https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/customizingParticles/
     */
    particles.forEach((particle, index) => {
      // ...
    })
  }

  // ...
}

來看看結果

text
{particles: 1163, time: 1.100000023841858}
{particles: 1168, time: 1.2000000476837158}
{particles: 1184, time: 1.2000000476837158}
{particles: 1180, time: 1}
{particles: 1171, time: 1}
{particles: 1161, time: 1.2000000476837158}
{particles: 1174, time: 1.1999999284744263}
{particles: 1166, time: 1.2999999523162842}

可以注意到耗時多了一點點,執行時間在 1 ~ 1.3 ms 之間。

for of

再來試試看 for of

ts
particleSystem.updateFunction = function (particles) {
  const startTime = performance.now()
  // 計算 staticMap 與粒子是否有碰撞
  for (const id in staticMap) {
    const bounding = staticMap[id]
    if (!bounding)
      continue

    /** 依照文件作法更新粒子
     *
     * https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/customizingParticles/
     */
    let index = -1
    for (const particle of particles) {
      index++
      // ...
    }
  }

  // ...
}

結果:

text
{particles: 1189, time: 1.2000000476837158}
{particles: 1186, time: 1.5}
{particles: 1185, time: 1.5}
{particles: 1182, time: 1.100000023841858}
{particles: 1190, time: 0.7999999523162842}
{particles: 1188, time: 1.100000023841858}
{particles: 1187, time: 1.399999976158142}
{particles: 1185, time: 2.1999999284744263}
{particles: 1181, time: 1.1999999284744263}
{particles: 1186, time: 1}

for 差不多,但是偶而會跑出 2 ms 的時間。

暴風雪!

差異不大好像不夠精彩,讓我們把粒子數量加好加滿,變成暴風雪看看!ლ(´∀`ლ)

snowstorm

超多雪!ᕕ( ゚ ∀。)ᕗ

for

text
{particles: 97970, time: 360.10000002384186}
{particles: 98122, time: 340}
{particles: 98118, time: 342.7000000476837}
{particles: 98035, time: 355.2000000476837}
{particles: 98069, time: 348.60000002384186}

forEach

text
{particles: 98275, time: 304.09999990463257}
{particles: 98491, time: 273.5}
{particles: 98092, time: 356}
{particles: 97463, time: 472.7000000476837}
{particles: 98080, time: 342}

for of

text
{particles: 99365, time: 122.20000004768372}
{particles: 99052, time: 181.59999990463257}
{particles: 98330, time: 289.3000000715256}
{particles: 97579, time: 390.7999999523163}
{particles: 98233, time: 329.3000000715256}

粒子數量大約在 98k 左右,可以看到結果很有趣:

  • forforEach 時間差不多,但是 for 比較穩定
  • for of 最快,但偶而會變慢。

以上就是這次的超不專業的不嚴謹實驗。੭ ˙ᗜ˙ )੭

究竟誰比較快,會因為情境、runtime、瀏覽器最佳化與程式寫法而有所不同,最好應該實際跑過 benchmark 再做選擇。

總結 🐟

  • 元素數量不多時,forEachfor 差異不大。
  • 性能會因情境、runtime、瀏覽器最佳化與程式寫法而有所不同,最好應該實際跑過 benchmark 再做選擇。