複雜度為 O(1) 的「最不常用」快取演算法的 Python 實現

demoZ發表於2015-10-15

這篇文章描述了怎麼用 Python 實現複雜度為 O(1) 的「最不常用」(Least Frequently Used, LFU)快取回收演算法。在 Ketan Shah、Anirban Mitra 和 Dhruv Matani的論文中有演算法描述。實現中的命名是按照論文中的命名。

LFU 快取回收機制對於 HTTP 快取網路代理是非常有用的,我們可以從快取中移除那些最不常使用的條目。

本文旨在設計一個其所有操作的時間複雜度都只有 O(1)的 LFU 快取演算法,這些操作包括了插入、訪問和刪除(回收)。

這個演算法中用了雙向連結串列。其一是用於訪問頻率,連結串列中的每個結點都包含一個連結串列,其中的元素有相同的訪問頻率。假設快取中有5個元素。有兩個元素被訪問了一次,三個元素被訪問了兩次。在這個例子中,訪問頻率列表有兩個結點(頻率為1和2)。第一個頻率結點的連結串列中有兩個結點,第二個頻率結點的連結串列中有三個結點。

overview

我們要怎麼構建它呢?我們需要的第一個物件是結點:

接下來是雙向連結串列。每個結點有 prev 和 next 屬性,分別等於前一個和下一個結點。head 被設為第一個結點,tail 被設為最後一個結點。

doubly_linked_list

我們可以為雙向連結串列定義方法來在連結串列尾部加入結點,插入結點,刪除結點以及獲得連結串列所有結點的資料。

訪問頻率雙向連結串列中的每個結點都是一個頻率結點(下圖中的Freq Node)。它是一個結點,同時也是一個包含有相同頻率的元素(下圖中Item node)的雙向性連結串列。每個條目結點都有一個指向其頻率結點父親的指標。

freq_item_lists

條目結點的資料等於我們要儲存的元素的鍵,這個鍵可以是一條HTTP請求。內容本身(例如HTTP響應)儲存在字典中。字典中的每個值是LfuItem型別,”data”是快取的內容,”parent”是指向頻率結點的指標,”node”是指向頻率結點下條目結點的指標。

lfu_item

我們已經定義了資料物件類,現在可以定義快取物件類了。它有一個雙向連結串列(訪問頻率連結串列)和一個包含LFU條目(上面的LfuItem)的字典。我們定義兩個方法:一個用來插入頻率結點,一個用來刪除頻率結點。

下一步是定義方法來插入到快取,訪問快取以及從快取中刪除。

我們來看看插入方法的邏輯。它以一個鍵和值為引數,例如HTTP請求和響應。如果沒有頻率為1的頻率結點,它就被插入到訪問頻率雙向連結串列的開頭。一個條目結點被加入到頻率結點的條目雙向連結串列。鍵和值被加入到字典中。複雜度是O(1)。

我們在快取中插入兩個元素,得到:

insert我們來看看訪問方法的邏輯。如果鍵不存在,我們丟擲異常。如果鍵存在,我們把條目結點移到頻率加一的頻率結點的連結串列中(如果頻率結點不存在就增加這個結點)。複雜度是O(1)。

如果我們訪問Key 1的條目,這個條目結點就被移動到頻率為2的頻率結點之下。我們得到:

access如果我們訪問Key 2的條目,這個條目結點就被移動到頻率為2的頻率結點之下。頻率為1的頻率結點會被刪除(譯註:因為它之下沒有條目結點了),我們得到:

access_2我們再看看delete_lfu方法。它把最不常使用的條目從快取中刪除。為此,它刪除第一個頻率結點下的第一個條目結點,同時從字典刪除對應的LFUItem物件。如果此操作過後,頻率結點的連結串列為空,就刪除這個頻率結點。

如果在快取上呼叫delete_lfu,資料為Key 1的條目結點和它的LFUItem將被刪除。我們得到:

delete_lfu

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

複雜度為 O(1) 的「最不常用」快取演算法的 Python 實現 複雜度為 O(1) 的「最不常用」快取演算法的 Python 實現

相關文章