在 reduce 使用點點點 spread operator 是效能上的 anti-pattern - Jason's Web Memo

在 reduce 使用點點點 spread operator 是效能上的 anti-pattern

隨著 functional programing 在 JavaScript 領域的盛行,跟 ECMAScript 標準的演進,JS 開發者在處理資料結構轉換上有了更多可以符合 immutable 的做法。於是下面的 pattern 越來越常在 array 轉成 object 裡面出現。

users.reduce((acc, item) => {
return { ...acc, [item.key]: item.value };
}, {});

但實際上我們來做個小實驗來比較下面這兩個例子的效能:

let users = Array.from({ length: 1000 }, (item, i) => ({ key: i, value: i }));
console.time();
let result = {};
users.forEach((acc, item) => {
result[item.key] = item.value;
});
console.timeEnd();

上面的算出 result js 執行時間是 0.1259 ms。

let users = Array.from({ length: 1000 }, (item, i) => ({ key: i, value: i }));
console.time();
let result = users.reduce((acc, item) => {
return { ...acc, [item.key]: item.value };
}, {});
console.timeEnd();

上面的算出 result js 執行時間是 81.2827 ms,整整比上面 forEach 的版本慢了 600 多倍。

為何 spread Operator 是慢的

其實 spread Operator 幫我們做的事情只是把一個 object 的 enumerable property 複製到另外一個 object,可以看作是在 reduce loop 裡面再跑一個 loop 遍歷所有的 object key 進行複製,計算出來的複雜度約略為 O(n^2),而單純 forEach 只有跑一個 loop, 複雜度是 O(n),這根本上的差異,造成 spread Operator in reduce 的效能上會隨著 n 的數值越大,差距會越來越明顯。

替代方案

的確 immutable 的手法讓我們能夠更肯定的數據的流向,不用擔心數據的意外操作,即使實務上我們願意犧牲一些效能來換取可讀性,但我們也不需要太過於矯枉過正,直接忽略到隱藏於其中的一個指數級別的效能問題。為了改善關鍵性能數量級讓數據可以被修改,我認爲是必須要做的事情。除了 forEach 以外我們也可以用下面做法來取代:

方法一: mutable accumulator in reduce

let users = Array.from({ length: 1000 }, (item, i) => ({ key: i, value: i }));
console.time();
let result = users.reduce((acc, item) => {
acc[item.key] = item.value;
return acc;
}, {});
console.timeEnd();

上面的算出 result js 執行時間是 0.1252 ms

方法二: object.fromEntries

let users = Array.from({ length: 1000 }, (item, i) => ({ key: i, value: i }));
console.time();
let result = Object.fromEntries(users.map((item) => [item.key, item.value]));
console.timeEnd();

上面的算出 result js 執行時間是 0.2631 ms

結語

這篇文章並非鼓吹我們為了效能應該全面放棄 immutable 的做法,事實上擁抱 immutable 的概念的確降低了很多閱讀維護程式碼的心智負擔,但 mutable 並非你想的那麼壞,有時候在關鍵效能上是很有用的。如果複雜度有數量級以上的差別,即使你的資料量不大,你仍應該要以複雜度低許多的演算法為優先,也比較不會因為習慣而導致你的程式上其他地方有嚴重的性能問題。如果複雜度沒有一個數量級以上的差別,我們就不需要在小地方糾結效能上有沒有改善。懂得權衡利弊挑選適當的工具處理問題,而並非一股腦地隨波逐流,也才是工程師的價值之所在。

Ref

Webmention 社群迴響 30

喜歡 24
轉推 1
引用或評論 5

用 Webmentions 參與社群迴響

透過 twitter 轉推,你的轉推評論之後會更新在上面社群迴響的引用評論列表。

轉推這篇文章

如果你的 blog 文章想要引用本文,歡迎透過下方表單用將你的 blog 文章網址傳送給我,若你的 blog 文章含有正確的本文網址連結,並且 blog 文章本身支持 microformat,之後你的引用資訊會更新在上面社群迴響的引用評論列表。

社群迴響將不定期更新,不保證同步,同時有資料缺漏的可能性。最新回應請參考以下連結:

Jason Chen - Yahoo Taiwan EC Web frontend engineer currently. Write something about web and React.js here

Jason Chen

Yahoo Taiwan Sr. Frontend Engineer. Write something about web and React.js here.

Other Posts