聽說 forEach 比 for 迴圈慢?
常聽到別人說:某些情況下 forEach
比 for
迴圈還慢。
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
部分維持不變,因為這次只比較矩陣 forEach
與 for
的差異。
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 的時間。
暴風雪!
差異不大好像不夠精彩,讓我們把粒子數量加好加滿,變成暴風雪看看!ლ(´∀`ლ)
超多雪!ᕕ( ゚ ∀。)ᕗ
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 左右,可以看到結果很有趣:
for
與forEach
時間差不多,但是for
比較穩定for of
最快,但偶而會變慢。
以上就是這次的超不專業的不嚴謹實驗。੭ ˙ᗜ˙ )੭
究竟誰比較快,會因為情境、runtime、瀏覽器最佳化與程式寫法而有所不同,最好應該實際跑過 benchmark 再做選擇。
總結 🐟
- 元素數量不多時,
forEach
與for
差異不大。 - 性能會因情境、runtime、瀏覽器最佳化與程式寫法而有所不同,最好應該實際跑過 benchmark 再做選擇。