Skip to content

你是個成熟的 function 了,該自己記得結果了

improve-speed-using-memoization

最近遇到從大矩陣資料找出指定資料的實作,例如某個矩陣定義了 200 多個資料定義,裡面包含了名稱、顏色、條件定義等等資料。

js
const data = [
  { name: 'A', color: 'red', condition: () => 'A' },
  { name: 'B', color: 'blue', condition: () => 'B' },
  { name: 'C', color: 'green', condition: () => 'C' },
  { name: 'D', color: 'yellow', condition: () => 'D' },
  { name: 'E', color: 'black', condition: () => 'E' },
  // ... 200 多個
]

想要取得符合條件的資料,最直覺的方式就是用 filter,例如:

ts
function getData(color: string) {
  return data.filter((item) => item.color === color)
}

由於 data 不會變動,output 只與 input 相關。

此 function 又會大量使用,可以預期有非常多重複的運算。

這時候就可以使用 memoization 這個技巧,透過記憶化來提升計算效能。

什麼是 memoization

Memoization 是一種簡單的技巧,透過記憶化來提升計算效能。

當 function 被呼叫時,會先檢查是否有相同的輸入值,如果有就直接回傳記憶的結果,而不是重新計算。

就是一個使用空間換時間的概念。◝( •ω• )◟

使用 Vue 的朋友們,如果有用過 computed,其實都用過 memoization 了。

有興趣的朋友們可以看看我先前的文章「善用 computed」,裡面有詳細探討。

Lodash memoize

這次我直接使用 Lodash 提供的 memoize 來實現 memoization。

口說無憑,讓我們實際跑跑看 benchmark,看看實際上差多少。

使用 BenchJS 這個酷炫的網站,可以直接在 Web 跑 benchmark,非常方便。

首先在 Code 分頁中,準備資料並撰寫實作。

首先在 setup.ts 裡面建立測試資料,export 後就可以在各實作中共用。

setup.ts

ts
function generateTestData(size: number) {
  return Array.from({ length: size }, (_, i) => ({
    key: `${i}`,
    value: i
  }))
}
export const length = 200
export const data = generateTestData(length)

接著在 implementations 中建立兩個檔案,檔名與實作相同。

implementations/memoize.ts

ts
import { memoize } from 'lodash'

const getData = memoize((key: string) =>
  data.find((datum) => datum.key === key),
)

export function run() {
  getData(`${length - 1}`)
}

implementations/no-memoize.ts

ts
const getData = (key: string) => data.find((datum) => datum.key === key)

export function run() {
  getData(`${length - 1}`)
}

可以看到資料與實作都一樣,只差在 memoize.tsgetData 有 memoize。

現在讓我們到 Compare 分頁,按下 Run All。

BenchJS 會開始跑 run 的內容,並且計算時間。

compare-result

可以看到 memoizeno-memoize 快非常多。

範例網址

為甚麼不要都 memoization 就好?

因為實作 memoization 也需要額外的計算與記憶體。

在資料很少、計算負荷極低的情況下,可能沒什麼差別,甚至可能比較慢。

在這次的例子中,把 length 改成 5,再跑一次 benchmark。

txt
memoize.ts     141,218,403   (-0.7%)
no-memoize.ts  142,192,549   Best

會看到結果很接近,甚至 no-memoize 還比 memoize 快一點。

所以實際上要看情況使用,不是所有情況都適合使用 memoization。

真的遇到性能瓶頸,需要進行改善時,再來評估改進方案。

記得要跑 benchmark,有多少證據,說多少話喔。( ´ ▽ ` )ノ

總結 🐟

  • memoization 可以提升效能,透過記憶化來避免重複計算。
  • 不是所有情況都適合使用 memoization,要看情況使用。
  • 記得要跑 benchmark,才能確定是否真的有提升效能。