天天看點

如何寫成高性能的代碼(三):巧用稀疏矩陣節省記憶體占用稀疏矩陣的概念稀疏矩陣的存儲方式及優化

稀疏矩陣的概念

一個m×n的矩陣是一個由m行n列元素排列成的矩形陣列。矩陣裡的元素可以是數字、符号及其他的類型的元素。

一般來說,在矩陣中,若數值為0的元素數目遠遠多于非0元素的數目,并且非0元素分布沒有規律時,則稱該矩陣為稀疏矩陣;與之相反,若非0元素數目占大多數時,則稱該矩陣為稠密矩陣。定義非零元素的總數比上矩陣所有元素的總數為矩陣的稠密度。,下面的矩陣就是一個典型的稀疏矩陣。

如何寫成高性能的代碼(三):巧用稀疏矩陣節省記憶體占用稀疏矩陣的概念稀疏矩陣的存儲方式及優化

我們日常使用的電子表格也是一個典型的稀疏矩陣場景,電子表格(SpreadJS, Excel,Google Sheet等等),整體看起來像一個table表格。,

其中列名稱依次為A, B, C … …, 行名稱依次為1, 2, 3 … …

舉例一個比較極端的場景,在A1和ZZ2000單元格分别指派,這樣我們就需要一個2000行,26*26+26=702列的矩陣來表示它,這個矩陣是一個明顯的稀疏矩陣。

稀疏矩陣的存儲方式及優化

直接存儲為二維矩陣

直接使用二維矩陣會簡單直接地存儲整個電子表格,這樣你不必每次都建立或删除一段記憶體。

但這是一種非常暴力的存儲值的方法,這種方式下會消耗大量内容來存儲毫無内容的單元格。

簡單的來看一下它的複雜度:

  • 占用空間:O(N2)
  • 插入資料:需要破壞矩陣.
  • 删除資料:需要破壞矩陣.
  • 搜尋資料:O(N2)
  • 通路資料:O(1)

N是假設行和列具有相同長度并形成正方形矩陣的行/列數。

通過鍵值對(Map, Dictionary)優化

在這種方法中,隻有在單元格有值時,我們才将單元格的值和位置存儲在一起,使用HashMap或者Dictionary這些資料結構可以很容易的做到.。

下圖我們可以看到,鍵值對中分别存儲了單元格位置和單元格值。

如何寫成高性能的代碼(三):巧用稀疏矩陣節省記憶體占用稀疏矩陣的概念稀疏矩陣的存儲方式及優化

來看一下它的複雜度:

  • 空間:O(N)
  • 插入:O(1)
  • 删除:O(1)
  • 搜尋:O(N)
  • 通路:O(1)

N為所記錄的條目數。

通過稀疏矩陣存儲方式優化

在稀疏矩陣中,我們可以使用三個不同的數組來存儲行索引、列偏移、和其中的值,而不是直接在二維矩陣中存儲值。以這種方式按列壓縮稀疏矩陣

存儲的三個數組:

  1. 值 =>單元格中的值。
  2. 行索引=>單元格的行索引。
  3. 列偏移=>這裡每個索引都代表列,并且該數組将行開始的索引值存儲在 Row 數組中。
如何寫成高性能的代碼(三):巧用稀疏矩陣節省記憶體占用稀疏矩陣的概念稀疏矩陣的存儲方式及優化

稀疏矩陣具體的插入,、删除,、搜尋,、通路的代碼,大家可以自己來搜尋,這方面的資料網上有很多。,這裡不一一列舉。

和上面一樣,來看看這種方式的複雜度:

  • 空間:O(N)
  • 插入:O(N)
  • 删除:O(N)
  • 搜尋:O(N)
  • 通路:O(1)

相較于傳統的數組存儲或是鍵值對存儲,稀疏矩陣存儲建構了基于行索引為 Key 的資料字典,在松散布局的表格資料中,稀疏矩陣隻會對非空資料進行存儲,而不需要對空資料開辟額外的記憶體空間。使用這種特殊的存儲政策,使得資料片段化變得容易,可以随時框取整個資料層中的一片資料,進行序列化或反序列化。如果我們在項目開發中需要存儲類似結構的資料,稀疏矩陣這種存儲方式,無論從時間還是空間上都能大大的提成性能。

在葡萄城的 SpreadJS 和 GcExcel 表格元件中,也巧妙的使用了稀疏矩陣這一特性,可以随時替換或恢複整個存儲結構中的任何一個級别的節點,以改變引用的方式更高效的地解決表格資料復原和恢複問題,而這一點也為葡萄城表格元件支援多人線上協同打下了一個良好的基礎。

由于底層采用了稀疏矩陣來優化存儲,SpreadJS在前端頁面中,實作了100W級别資料秒級響應的能力:

如何寫成高性能的代碼(三):巧用稀疏矩陣節省記憶體占用稀疏矩陣的概念稀疏矩陣的存儲方式及優化

純前端百萬級資料請求、加載、篩選和排序

點選此處可以線上體驗:性能示範

同時,結合SpreadJS中使用的Canvas 繪制模型,雙緩存畫布渲染等專利技術,讓SpreadJS的前端性能相比于同類産品遙遙領先。