12.哈希庫
DPDK提供了一個(gè)用于創(chuàng)建哈希表的哈希庫,哈希表可以用于快速查找。哈希表是針對一組條目進(jìn)行搜索而優(yōu)化的數(shù)據(jù)結(jié)構(gòu),每個(gè)條目由唯一Key標(biāo)識。為了提高性能,DPDK哈希要求所有的Key值具有與哈希創(chuàng)建時(shí)指定的相同字節(jié)數(shù)。
12.1.哈希API概述
哈希的主要配置參數(shù)包括:
- 哈希條目總數(shù)(哈希容量)。
- Key的字節(jié)數(shù)。
哈希還允許配置一些低級實(shí)現(xiàn)相關(guān)的參數(shù):
- 將Key轉(zhuǎn)換為哈希桶索引值的哈希函數(shù)
哈希庫導(dǎo)出的主要方法包括:
- 使用Key值添加條目:Key值作為輸入?yún)?shù)。如果新條目成功添加到指定Key的哈希中,或者已經(jīng)有指定Key的條目,則返回該條目的位置。如果操作不成功,例如由于在哈希中缺少空閑條目,則返回負(fù)值;
- 使用Key刪除條目:Key值作為輸入?yún)?shù)。如果在哈希中找到具有指定Key的條目,則會從哈希中刪除該條目,并返回該條目在哈希中找到的位置。如果哈希中沒有指定Key的條目存在,則返回一個(gè)負(fù)值;
- 使用Key查找條目:Key值作為輸入?yún)?shù)。如果在哈希(查找命中)中找到具有指定Key的條目,則返回條目的位置,否則返回(查詢未命中)一個(gè)負(fù)值。
除了這些方法,API還為用戶提供了三個(gè)選項(xiàng):
- 使用Key和precomputed hash來查找/添加/刪除條目:Key及其precomputed hash都作為輸入。這允許用戶更快地執(zhí)行操作,因?yàn)橐呀?jīng)預(yù)先計(jì)算了散列。
- 使用Key和數(shù)據(jù)來查找/添加條目:提供Key-value作為輸入。這允許用戶不僅存儲Key,還可以存儲8byte的整形或是一個(gè)指向外部數(shù)據(jù)的指針(數(shù)據(jù)超過8byte)。
- 上述兩個(gè)選項(xiàng)的組合:用戶可以提供Key、precomputed hash或是data。
此外,API包含一種方法,允許用戶在突發(fā)中查找條目,實(shí)現(xiàn)比查找單個(gè)條目更高的性能,因?yàn)樵摵瘮?shù)在與第一個(gè)條目操作時(shí)預(yù)取下一個(gè)條目,顯著降低了必要的內(nèi)存訪問。請注意,此方法使用8個(gè)條目(4個(gè)階段2條目)的流水線,因此強(qiáng)烈建議每個(gè)突發(fā)使用至少8個(gè)條目。
與每個(gè)Key相關(guān)聯(lián)的實(shí)際數(shù)據(jù)可以由用戶使用單獨(dú)的表格進(jìn)行管理,該表格根據(jù)哈希條目數(shù)量和每個(gè)條目的位置來反映哈希表,如以下部分中描述的流分類用例所示,當(dāng)然,也可以直接存儲在哈希表本身。
L2 / L3轉(zhuǎn)發(fā)示例應(yīng)用程序中的哈希表根據(jù)由五元組查找標(biāo)識的數(shù)據(jù)包流定義將數(shù)據(jù)包轉(zhuǎn)發(fā)到哪個(gè)端口。然而,該表還可以用于更復(fù)雜的特征,并提供可以在分組和流上執(zhí)行的許多其他功能和動作。
12.2.多進(jìn)程支持
哈希庫可以在多進(jìn)程環(huán)境中使用,只需查找線程安全即可。只能在單進(jìn)程模式下使用的唯一函數(shù)是rte_hash_set_cmp_func(),它設(shè)置一個(gè)自定義的比較功能,分配給一個(gè)函數(shù)指針(因此在多進(jìn)程模式下不支持)。
12.3.實(shí)現(xiàn)細(xì)節(jié)
哈希表有兩個(gè)主表:
- 第一個(gè)表是一組條目,進(jìn)一步分為桶,每個(gè)桶中具有相同數(shù)量的連續(xù)數(shù)組條目。每個(gè)條目包含計(jì)算的給定Key的主要和次要散列(如下所述)和第二個(gè)表的索引。
- 第二個(gè)表是存儲在哈希表中的所有Key的數(shù)組及其與每個(gè)Key相關(guān)聯(lián)的數(shù)據(jù)。
哈希庫使用Cuckoo hash(布谷鳥散列)方法來解決沖突。對于任何輸入Key,有兩個(gè)可能的桶(主要和次要/替代位置),其中該Key可以存儲在散列中,因此只有當(dāng)查詢Key時(shí)才需要檢查桶中的條目。與通過線性掃描陣列中的所有條目的基本方法相反,通過將散列條目的總數(shù)減少到兩個(gè)哈希桶中的條目數(shù)來減少要掃描的條目數(shù)以提升查找速度。哈希使用散列函數(shù)(可配置)將輸入Key轉(zhuǎn)換為4字節(jié)Key簽名。桶索引值是將哈希Key簽名對哈希桶數(shù)取模數(shù)的值。
一旦識別出桶,哈希添加,刪除和查找操作的范圍就減少到這些存儲區(qū)中的條目(很可能條目在主存儲桶中)。
為了加快桶內(nèi)的搜索邏輯,每個(gè)散列條目將4字節(jié)Key簽名與每個(gè)哈希條目的完整Key一起存儲。對于大的Key,將輸入Key與來自存儲桶的Key進(jìn)行比較比將輸入Key的4字節(jié)簽名與來自存儲桶的Key簽名進(jìn)行比較要花費(fèi)更多的時(shí)間。因此,首先完成簽名比較,僅在簽名匹配時(shí)才完成Key比較。完全Key比較仍然是必要的,因?yàn)閬碜韵嗤鎯ν暗膬蓚€(gè)輸入Key仍然可能具有相同的4字節(jié)簽名,盡管對于該組輸入密鑰提供良好的均勻分布的散列函數(shù),該事件相對較少。
查找實(shí)例:
首先,主桶被識別,條目可能存儲在那里。如果簽名存儲在那里,我們將其Key與提供的Key進(jìn)行比較,并返回其存儲位置和/或與該密鑰相關(guān)聯(lián)的數(shù)據(jù)(如果有匹配)。如果簽名不在主桶中,則查找輔助桶,在那里執(zhí)行相同的過程。如果沒有匹配,Key對應(yīng)條目被認(rèn)為不在表中。添加實(shí)例:
像查找操作一樣,Key標(biāo)識主和二級桶。如果主桶中有一個(gè)空槽,則主簽名和輔助簽名存儲在該槽中,Key和數(shù)據(jù)(如果有的話)被添加到第二個(gè)表中,并且第二個(gè)表中的位置的索引被存儲在第一張表上。如果主桶中沒有空槽,則該桶中的一個(gè)條目將被推送到其替代位置,并將要添加的Key插入第一個(gè)條目的位置上。要知道驅(qū)逐條目(第一個(gè)條目)的替代桶的哪個(gè)位置,則查找器輔助簽名,并從上面的模數(shù)中計(jì)算備用桶索引。如果替代桶中有空間,則將被驅(qū)入的條目存儲在其中。如果沒有,則重復(fù)相同的過程(其中一個(gè)條目被推送),直到找到非完整的數(shù)據(jù)桶。請注意,盡管所有的條目移動都在第一張表中,第二張表沒有被觸動,這也將在性能上受到很大影響。
在非常不太可能的事件中,該表進(jìn)入循環(huán),其中相同的條目被無限期地驅(qū)逐,則認(rèn)為Key不能被存儲。使用隨機(jī)Key,該方法允許用戶獲取約90%的表利用率,而不必放棄任何存儲的條目(LRU)或分配更多內(nèi)存(擴(kuò)展桶)。
12.4.哈希表中的條目分發(fā)
如上所述,如果有一個(gè)新的條目要被添加到哪個(gè)主桶,而當(dāng)前已經(jīng)有數(shù)據(jù)在里面時(shí),則將數(shù)據(jù)推送到他們的替代位置,Cuckoo哈希實(shí)現(xiàn)了將元素推出他們的存儲區(qū)。
因此,當(dāng)用戶向哈希表添加更多條目時(shí),桶中散列值的分布將發(fā)生變化,其中大部分位于主要位置,并且其次要位置會隨之增加,隨后表將增加。
這些信息是非常有用的,因?yàn)殡S著更多條目逐出其次要位置,性能可能會降低。
下表顯示了表利用率增加時(shí)的示例條目分布。
Table 12.1 Entry distribution measured with an example table with 1024 random entries using jhash algorithm
%Table used | %In Primary location | %In Secondary location |
---|---|---|
25 | 100 | 0 |
50 | 96.1 | 3.9 |
75 | 88.2 | 11.8 |
80 | 86.3 | 13.7 |
85 | 83.1 | 16.9 |
90 | 77.3 | 22.7 |
95.8 | 64.5 | 35.5 |
Table 12.2 Entry distribution measured with an example table with 1 million random entries using jhash algorithm
%Table used | %In Primary location | %In Secondary location |
---|---|---|
50 | 96 | 4 |
75 | 86.9 | 13.1 |
80 | 83.9 | 16.1 |
85 | 80.1 | 19.9 |
90 | 74.8 | 25.2 |
94.5 | 67.4 | 32.6 |
注意:上表上的最后值是具有隨機(jī)密鑰和使用Jenkins散列函數(shù)的平均最大表利用率。
12.5.用例:流分類
流分類用于將每個(gè)輸入數(shù)據(jù)包映射到它所屬的連接/流。這種操作是必需的,因?yàn)槊總€(gè)輸入分組的處理通常在其連接的上下文中進(jìn)行,因此相同的操作集合被應(yīng)用于來自相同流的所有分組。
使用流分類的應(yīng)用通常具有要管理的流表,每個(gè)單獨(dú)的流具有與該表相關(guān)聯(lián)的條目。流表?xiàng)l目的大小是特定于應(yīng)用程序的,典型值為4,16,32或64字節(jié)。
使用流分類的每個(gè)應(yīng)用通常具有被定義為從輸入報(bào)文中讀取一個(gè)或多個(gè)字段來構(gòu)成Key,用于標(biāo)識流。一個(gè)例子是使用由IP和傳輸層數(shù)據(jù)包頭的以下字段組成的DiffServ 5元組:源IP地址,目標(biāo)IP地址,協(xié)議,源端口,目標(biāo)端口。
DPDK哈希提供了一種通用的方法來實(shí)現(xiàn)應(yīng)用程序指定的流分類機(jī)制。 給定一個(gè)用數(shù)組實(shí)現(xiàn)的流表,應(yīng)用程序應(yīng)該創(chuàng)建與流表相同數(shù)量的條目的哈希對象,并將哈希密鑰大小設(shè)置為所選流Key中的字節(jié)數(shù)。
應(yīng)用側(cè)的流程表操作如下:
- 添加流:將流Key添加到哈希。如果返回的位置有效,則使用它來訪問流表中用于添加新流或更新與現(xiàn)有流相關(guān)聯(lián)的信息的流條目。否則,流添加失敗,例如由于缺少用于存儲新流的空閑條目。
- 刪除流:從哈希中刪除流Key。如果返回的位置有效,則使用它來訪問流表中的流條目以使與流相關(guān)聯(lián)的信息無效。
- 查找流:在哈希中查找流Key。如果返回的位置有效(流查找命中),則使用返回的位置來訪問流表中的流條目。否則(流查找未命中)表示當(dāng)前數(shù)據(jù)包沒有注冊流。
12.6.參考
Donald E. Knuth, The Art of Computer Programming, Volume 3: Sorting and Searching (2nd Edition), 1998, Addison-Wesley Professional