DPDK Hash Library原理(學習筆記)


0 前言

本文主要翻譯至DPDK的官方編程指南,在谷歌翻譯的基礎上根據自己的理解做了一些修改。網上搜索的很多中文翻譯大多是翻譯后直接黏貼上來,有時候連語句都讀不通。希望本文能夠對你有所幫助。

1 介紹

DPDK提供了一個哈希庫,用於創建用於快速查找的哈希表。哈希表是一種數據結構,它經過優化,用於搜索由唯一鍵標識的一組哈希條目。為了提高性能,DPDK散列要求所有鍵在創建散列時具有相同的字節數。

2 Hash API概述

哈希表的主要配置參數有:

  • 表中哈希條目的總數
  • 鍵的大小(以字節為單位)
  • 用於描述其他設置的額外標志,例如多線程操作模式和可擴展桶功能(稍后將進行描述)

哈希表還允許配置一些底層實現相關參數,如:

  • 將鍵轉換為哈希值的哈希函數

哈希庫的主要方法有:

  • 添加帶有鍵的條目:鍵作為輸入參數。如果新條目成功地添加到指定鍵的哈希表中,或者指定鍵的哈希表中已經有一個條目,則返回該條目的位置。如果操作不成功,例如由於哈希表中缺少空閑條目,則返回一個負值。
  • 帶鍵刪除條目:鍵作為輸入參數。如果在哈希表中找到具有指定鍵的條目,則從哈希表中刪除該條目,並返回在哈希表中找到該條目的位置。如果哈希表中不存在具有指定鍵的條目,則返回一個負值
  • 查找帶有鍵的條目:鍵作為輸入參數。如果在哈希表中找到具有指定鍵的條目(即,查找命中),則返回條目的位置,否則(即,查找未找到)返回負值。

除了上述基本方法之外,哈希庫API還提供了一些更高級的方法來查詢和更新哈希表:

  • 添加/查找/刪除帶有鍵和預計算哈希值的條目:鍵及其預計算的哈希值均作為輸入參數,這使用戶能更快地執行這些操作,因為已經計算了哈希值。
  • 添加/查找條目並帶上鍵和數據:添加時不僅允許用戶存儲鍵,而且可以帶上數據,一個8字節的整數或指向外部數據的指針(如果數據大小超過8個字節)。鍵和數據都作為輸入參數。
  • 以上兩個選項的組合:用戶可以提供鍵、預先計算的哈希值以及數據。
  • 能夠在調用delete時不釋放哈希表中條目的位置。這對於多線程場景非常有用,在這種情況下,即使條目被刪除,讀者仍然會繼續使用該位置。

API還包含一個方法允許用戶批量查找條目,這比單個條目查找性能更高,查找函數在執行當前操作時預取下一個條目,這極大地降低了內存訪問導致的性能開銷。

用戶可以使用單獨的表來管理與每個鍵關聯的實際數據,該表記錄了哈希的條目數和每個條目的位置,如以下各節所述的“流分類”用例所示,用戶也可以存儲實際數據在哈希表本身中。

在L2/L3轉發示例中,程序根據包的五元組信息在哈希表中查找對應的流,再決定要將包轉發到哪個端口。當然,這個表也可以用於更復雜的特性,並提供許多可以在包和流上執行的其他功能和操作。

2 多進程支持

哈希庫可以在多進程環境中使用。 唯一的只能在單進程模式下使用的函數是rte_hash_set_cmp_func(),該函數用於設置一個自定義的比較函數,由於該函數被分配給當前進程的函數指針(因此在多進程模式下不支持)。

3 多線程支持

哈希庫支持多線程,並且用戶可以在哈希表創建時通過設置適當的標志來指定所需的操作模式。 在所有操作模式下,查找都是線程安全的,這意味着可以從多個線程同時調用查找。

對於並發寫入和並發讀寫,以下標志值定義了相應的操作模式:

  • 如果設置了“多個寫者”的標志(RTE_HASH_EXTRA_FLAGS_MULTI_WRITER_ADD),則允許多個線程寫入表。鍵的添加、刪除和表重置受到保護。如果僅設置此標志,無法保護“讀者”免受正在進行的寫入操作的影響。
  • 如果設置了讀/寫並發(RTE_HASH_EXTRA_FLAGS_RW_CONCURRENCY),則多線程讀/寫操作是安全的(即,應用程序不需要“停止讀者訪問哈希表,一直等待寫者完成更新為止”。讀者和寫者可以同時在表上進行操作 )。該庫使用讀寫鎖提供並發。
  • 除了這兩個標志值外,如果還設置了事務性內存標志(RTE_HASH_EXTRA_FLAGS_TRANS_MEM_SUPPORT),那么如果硬件支持,讀寫鎖將使用硬件事務性內存(例如Intel®TSX)來保證線程安全。如果平台支持Intel®TSX,建議設置事務性內存標志,因為這將加快並發表操作。否則,由於軟件鎖定機制相關的開銷,並發操作將變慢。
  • 如果設置了無鎖讀/寫並發(RTE_HASH_EXTRA_FLAGS_RW_CONCURRENCY_LF),則將提供不使用讀寫鎖的讀/寫並發。對於不支持事務性內存的平台(例如,當前基於ARM的平台),建議設置此標志以實現更高的性能可伸縮性。如果設置了此標志,則默認設置(RTE_HASH_EXTRA_FLAGS_NO_FREE_ON_DEL)標志。
  • 如果設置了“在刪除時不釋放”(RTE_HASH_EXTRA_FLAGS_NO_FREE_ON_DEL)標志,則在調用delete()時不會釋放哈希表中條目的位置。當設置無鎖讀/寫並發標志時,默認情況下啟用此標志。在所有讀者都停止引用該位置之后,應用程序應釋放該位置。在需要時,應用程序可以利用RCU機制來確定讀者何時停止引用該位置。

4 可擴展的桶(Bucket )功能支持

通過額外的標志來啟用此功能(默認情況下未設置)。設置(RTE_HASH_EXTRA_FLAGS_EXT_TABLE)時(在極不可能的情況下,由於哈希沖突過多而導致無法插入鍵盤),哈希桶將使用鏈表進行擴展,以插入這些失敗的鍵。 對於插入哈希表的鍵個數能達到容量的100%且不能容忍任何鍵插入失敗(即使很少)的工作負載(例如,電信工作負載),此功能非常重要。 請注意,啟用“無鎖讀寫並發”標志后,用戶需要調用“ rte_hash_free_key_with_position” API才能釋放空存儲桶和已刪除的鍵,以保持100%的容量保證。

5 實施細節(不可擴展的桶)

哈希表有兩個主要的表:

  • 第一張表是一個桶的數組,每個桶都包含多個條目,每個條目都包含給定鍵的簽名(在下面說明),以及到第二個表的索引。注:下文提到的主和備桶都是在這張表里。
  • 第二張表也是一個數組,它存儲了哈希表中所有的鍵及其與每個鍵關聯的數據。

哈希庫使用Cuckoo哈希算法來解決沖突。對於任何輸入鍵,都有兩個可能的桶(主和備)用來將該鍵存儲在哈希表中(最終只會存在主或備中的一處),因此在查找鍵時僅需要檢查這兩個存儲區中的條目。哈希庫使用哈希函數(函數可配置)將輸入鍵轉換為4字節哈希值。使用部分鍵哈希[partial-key],從哈希值中獲取桶索引和2字節簽名。

一旦桶被標識后,鍵添的加、刪除和查找操作的范圍將縮小到這兩個桶中的條目(很有可能條目位於主存儲桶中)。

為了加快桶的搜索邏輯,每個哈希條目將2字節鍵簽名與每個哈希表條目的完整鍵一起存儲。對於較大的輸入鍵,如果直接將它與桶中保存的鍵進行比較,會比將它的2字節簽名與桶中的鍵簽名進行比較花費更多的時間。因此,僅在簽名匹配時才進行完整鍵的比較。我們仍然需要進行完整鍵比較,是因為來自同一個桶的兩個輸入鍵仍可能具有相同的2字節簽名,當然這個事件相對較少,因為哈希函數為輸入鍵集提供良好的均勻分布。

查找示例:

首先,計算哈希值並得到2字節簽名和主桶的索引。如果簽名存儲在此處(很有可能),我們將其鍵與提供的鍵進行比較,如果匹配,則返回其存儲位置和/或與該鍵關聯的數據。如果簽名不在主桶中,則在第二個桶(備桶)中查找,並執行相同的步驟。如果兩者都不匹配,則該鍵不在表中,將返回負值。

添加示例:

像查找一樣,主桶和備桶也得​​到了識別。如果主存儲桶中有一個空條目,則將簽名存儲在該條目中,並將鍵和數據(如果有)添加到第二個表中,並將第二個表中的索引存儲在第一個表的條目中。如果主桶中沒有空間,則將該桶上的條目之一推送到其備桶位置,然后將要添加的鍵插入其位置。為了知道被驅逐條目的替代存儲桶在哪里,使用了一種稱為部分鍵哈希[partial-key]的機制。如果備桶中有空間,被逐出的條目將存儲在其中。如果沒有,則重復相同的過程(其中一個條目被推送),直到找到一個空條目。請注意,盡管第一個表中有條目移動,但第二個表卻沒有被觸及,這將對性能產生很大影響。

在極少數情況下,經過一定數量的移位后找不到空條目,則認為無法添加鍵(除非設置了可擴展桶標志,在這種情況下桶被擴展以插入鍵, 稍后會說明)。 如果鍵是隨機的,此方法將使用戶獲得90%以上的表利用率,而不必刪除任何存儲的條目(例如使用LRU替換策略)或分配更多的內存(可擴展的存儲桶或重新哈希)。

刪除示例:

與查找類似,該鍵在其主桶和備桶中進行搜索。 如果找到了鍵,則將該條目標記為空。 如果哈希表配置為“刪除時無釋放”或“無鎖讀/寫並發”,則鍵的位置不會釋放。 在讀者不再引用該位置之后,用戶有責任釋放該位置。

6 實施細節(帶有可擴展桶)

設置RTE_HASH_EXTRA_FLAGS_EXT_TABLE標志后,哈希表實現仍使用相同的Cuckoo哈希算法將鍵存儲到第一表和第二表中。但是,在極少數情況下,在達到一定數量的布谷鳥移位之后還是無法插入鍵,此鍵的備桶將擴展帶有額外桶的鏈表,並且該鍵將存儲在此鏈表中。

在查找某個鍵的情況下,如前所述,在主、備桶中查找都沒有匹配項,則會在擴展桶(額外桶的鏈表)中逐一查找可能的匹配項,如果沒有匹配項,則認為該鍵不在表中。

刪除方法與未設置RTE_HASH_EXTRA_FLAGS_EXT_TABLE標志的情況基本相同,除了以下這點:如果從任何桶中刪除了一個鍵並創建了一個空位置,則與此桶相關聯的擴展桶中的最后一個條目將被移入該空位置,從而縮短鏈表。

7 哈希表中的條目分布

如上所述,在Cuckoo哈希實現里會將條目推入備桶位置, 因此,隨着用戶向哈希表添加條目的增多,其在桶中的分布將發生變化,其中大多數位於主桶位置,少數位於備桶位置,隨着條目增加,備桶位置將增加。 此信息非常有用,因為隨着更多條目被逐出到備桶位置,性能可能會降低。

請參閱下表,其中顯示了隨着表利用率的提高而產生的條目分布示例。

例1:使用jhash算法在帶有1024個隨機條目的示例表測量條目分布

image

例2:使用jhash算法,通過具有100萬隨機條目的示例表測量條目分布

image

注:表中的值是使用隨機鍵和Jhash的平均最大表利用率。

8 用例:流分類

流分類用於將每個輸入數據包映射到它所屬的連接/流。該操作是必需的,因為每個輸入數據包的處理通常是在它們的連接上下文中完成的,因此,將同一組操作應用於來自同一條流的所有數據包。

使用流分類的應用程序通常要管理流表,每條流在此表中都有與其相關聯的條目。流表條目的大小是由應用程序指定,典型值為4、16、32或64個字節。

使用流分類的應用程序通常從數據包中讀取多個字段組成鍵,依次來唯一標識流。例如使用數據包的IP和傳輸層頭的以下字段組成的5元組:源IP地址,目標IP地址,協議,源端口,目標端口。

DPDK哈希提供了一種通用方法來實現應用程序的流分類機制。給定一個實現為數組的流表,應用程序應創建一個條目數與流表相同的哈希對象,並且哈希鍵大小設置為所選流鍵(如五元組)的字節數。

應用程序側的流表操作如下所述:

  • 添加流:將流鍵添加到哈希。如果返回的位置有效,則使用它來訪問流表中的流條目,以添加新流或更新與現有流關聯的信息。否則,流添加失敗,例如,由於缺少用於存儲新流的空閑條目。
  • 刪除流:從哈希表中刪除流鍵。如果返回的位置有效,則使用它來訪問流表中的流條目,以使與流關聯的信息失效。
  • 釋放流:釋放流鍵位置。如果設置了“刪除時不釋放”或“無鎖的讀/寫並發”標志,請等到讀取器沒有引用添加/刪除流程中返回的位置,然后釋放該位置。 RCU機制可用於確定讀者何時不再參考該位置。
  • 查找流:在哈希中查找流鍵。如果返回的位置有效(流查找命中),則使用返回的位置訪問流表中的流條目。否則(流查找未命中)表示當前數據包沒有注冊流。

9 參考文獻

  • DPDK官方文檔:http://doc.dpdk.org/guides-20.02/prog_guide/hash_lib.html
  • Donald E. Knuth, The Art of Computer Programming, Volume 3: Sorting and Searching (2nd Edition), 1998, Addison-Wesley Professional
  • [partial-key] Bin Fan, David G. Andersen, and Michael Kaminsky, MemC3: compact and concurrent MemCache with dumber caching and smarter hashing, 2013, NSDI


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM