本文主要翻译至DPDK的官方编程指南,在谷歌翻译的基础上根据自己的理解做了一些修改。网上搜索的很多中文翻译大多是翻译后直接黏贴上来,有时候连语句都读不通。希望本文能够对你有所帮助。
DPDK提供了一个哈希库,用于创建用于快速查找的哈希表。哈希表是一种数据结构,它经过优化,用于搜索由唯一键标识的一组哈希条目。为了提高性能,DPDK散列要求所有键在创建散列时具有相同的字节数。
哈希表的主要配置参数有:
哈希表还允许配置一些底层实现相关参数,如:
哈希库的主要方法有:
除了上述基本方法之外,哈希库API还提供了一些更高级的方法来查询和更新哈希表:
API还包含一个方法允许用户批量查找条目,这比单个条目查找性能更高,查找函数在执行当前操作时预取下一个条目,这极大地降低了内存访问导致的性能开销。
用户可以使用单独的表来管理与每个键关联的实际数据,该表记录了哈希的条目数和每个条目的位置,如以下各节所述的“流分类”用例所示,用户也可以存储实际数据在哈希表本身中。
在L2/L3转发示例中,程序根据包的五元组信息在哈希表中查找对应的流,再决定要将包转发到哪个端口。当然,这个表也可以用于更复杂的特性,并提供许多可以在包和流上执行的其他功能和操作。
哈希库可以在多进程环境中使用。 唯一的只能在单进程模式下使用的函数是rte_hash_set_cmp_func(),该函数用于设置一个自定义的比较函数,由于该函数被分配给当前进程的函数指针(因此在多进程模式下不支持)。
哈希库支持多线程,并且用户可以在哈希表创建时通过设置适当的标志来指定所需的操作模式。 在所有操作模式下,查找都是线程安全的,这意味着可以从多个线程同时调用查找。
对于并发写入和并发读写,以下标志值定义了相应的操作模式:
通过额外的标志来启用此功能(默认情况下未设置)。设置(RTE_HASH_EXTRA_FLAGS_EXT_TABLE)时(在极不可能的情况下,由于哈希冲突过多而导致无法插入键盘),哈希桶将使用链表进行扩展,以插入这些失败的键。 对于插入哈希表的键个数能达到容量的100%且不能容忍任何键插入失败(即使很少)的工作负载(例如,电信工作负载),此功能非常重要。 请注意,启用“无锁读写并发”标志后,用户需要调用“ rte_hash_free_key_with_position” API才能释放空存储桶和已删除的键,以保持100%的容量保证。
哈希表有两个主要的表:
哈希库使用Cuckoo哈希算法来解决冲突。对于任何输入键,都有两个可能的桶(主和备)用来将该键存储在哈希表中(最终只会存在主或备中的一处),因此在查找键时仅需要检查这两个存储区中的条目。哈希库使用哈希函数(函数可配置)将输入键转换为4字节哈希值。使用部分键哈希[partial-key],从哈希值中获取桶索引和2字节签名。
一旦桶被标识后,键添的加、删除和查找操作的范围将缩小到这两个桶中的条目(很有可能条目位于主存储桶中)。
为了加快桶的搜索逻辑,每个哈希条目将2字节键签名与每个哈希表条目的完整键一起存储。对于较大的输入键,如果直接将它与桶中保存的键进行比较,会比将它的2字节签名与桶中的键签名进行比较花费更多的时间。因此,仅在签名匹配时才进行完整键的比较。我们仍然需要进行完整键比较,是因为来自同一个桶的两个输入键仍可能具有相同的2字节签名,当然这个事件相对较少,因为哈希函数为输入键集提供良好的均匀分布。
查找示例:
首先,计算哈希值并得到2字节签名和主桶的索引。如果签名存储在此处(很有可能),我们将其键与提供的键进行比较,如果匹配,则返回其存储位置和/或与该键关联的数据。如果签名不在主桶中,则在第二个桶(备桶)中查找,并执行相同的步骤。如果两者都不匹配,则该键不在表中,将返回负值。
添加示例:
像查找一样,主桶和备桶也得??到了识别。如果主存储桶中有一个空条目,则将签名存储在该条目中,并将键和数据(如果有)添加到第二个表中,并将第二个表中的索引存储在第一个表的条目中。如果主桶中没有空间,则将该桶上的条目之一推送到其备桶位置,然后将要添加的键插入其位置。为了知道被驱逐条目的替代存储桶在哪里,使用了一种称为部分键哈希[partial-key]的机制。如果备桶中有空间,被逐出的条目将存储在其中。如果没有,则重复相同的过程(其中一个条目被推送),直到找到一个空条目。请注意,尽管第一个表中有条目移动,但第二个表却没有被触及,这将对性能产生很大影响。
在极少数情况下,经过一定数量的移位后找不到空条目,则认为无法添加键(除非设置了可扩展桶标志,在这种情况下桶被扩展以插入键, 稍后会说明)。 如果键是随机的,此方法将使用户获得90%以上的表利用率,而不必删除任何存储的条目(例如使用LRU替换策略)或分配更多的内存(可扩展的存储桶或重新哈希)。
删除示例:
与查找类似,该键在其主桶和备桶中进行搜索。 如果找到了键,则将该条目标记为空。 如果哈希表配置为“删除时无释放”或“无锁读/写并发”,则键的位置不会释放。 在读者不再引用该位置之后,用户有责任释放该位置。
设置RTE_HASH_EXTRA_FLAGS_EXT_TABLE标志后,哈希表实现仍使用相同的Cuckoo哈希算法将键存储到第一表和第二表中。但是,在极少数情况下,在达到一定数量的布谷鸟移位之后还是无法插入键,此键的备桶将扩展带有额外桶的链表,并且该键将存储在此链表中。
在查找某个键的情况下,如前所述,在主、备桶中查找都没有匹配项,则会在扩展桶(额外桶的链表)中逐一查找可能的匹配项,如果没有匹配项,则认为该键不在表中。
删除方法与未设置RTE_HASH_EXTRA_FLAGS_EXT_TABLE标志的情况基本相同,除了以下这点:如果从任何桶中删除了一个键并创建了一个空位置,则与此桶相关联的扩展桶中的最后一个条目将被移入该空位置,从而缩短链表。
如上所述,在Cuckoo哈希实现里会将条目推入备桶位置, 因此,随着用户向哈希表添加条目的增多,其在桶中的分布将发生变化,其中大多数位于主桶位置,少数位于备桶位置,随着条目增加,备桶位置将增加。 此信息非常有用,因为随着更多条目被逐出到备桶位置,性能可能会降低。
请参阅下表,其中显示了随着表利用率的提高而产生的条目分布示例。
例1:使用jhash算法在带有1024个随机条目的示例表测量条目分布
例2:使用jhash算法,通过具有100万随机条目的示例表测量条目分布
注:表中的值是使用随机键和Jhash的平均最大表利用率。
流分类用于将每个输入数据包映射到它所属的连接/流。该操作是必需的,因为每个输入数据包的处理通常是在它们的连接上下文中完成的,因此,将同一组操作应用于来自同一条流的所有数据包。
使用流分类的应用程序通常要管理流表,每条流在此表中都有与其相关联的条目。流表条目的大小是由应用程序指定,典型值为4、16、32或64个字节。
使用流分类的应用程序通常从数据包中读取多个字段组成键,依次来唯一标识流。例如使用数据包的IP和传输层头的以下字段组成的5元组:源IP地址,目标IP地址,协议,源端口,目标端口。
DPDK哈希提供了一种通用方法来实现应用程序的流分类机制。给定一个实现为数组的流表,应用程序应创建一个条目数与流表相同的哈希对象,并且哈希键大小设置为所选流键(如五元组)的字节数。
应用程序侧的流表操作如下所述:
原文:https://www.cnblogs.com/realjimmy/p/12911004.html