在 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.

訂閱 blog 更新 開啟小鈴鐺

複製 RSS xml 網址

訂閱 Google Groups 電子報

追蹤我的 Medium

--

Other Posts