Open vSwitch 源码阅读笔记(3)OVS 中哈希表的实现

一、Open vSwitch 中的哈希表

        在 Open vSwitch 中存在多种类型的哈希表,如 hmap、smap、simap 和 shash 等,其中 hmap 是最基础的哈希表数据结构,而其他的哈希表都是在 hmap 的基础上进行实现的。在 Open vSwitch 中,很多内容都需要通过哈希表结构进行存储,所以各种类型的哈希表在源码中很常见。

二、hmap 的实现

        基础的哈希表 hmap 的结构体和相应的方法在 ovs-main/include/openvswitch/hmap.h 头文件和 ovs-main/include/openvswitch/hmap.c 文件中进行定义和实现。(此处为了节约空间,对代码做了省略和细微调整)。

(1)hmap 哈希表结构体

/* A hash map. */
struct hmap {
    struct hmap_node **buckets; /* Must point to 'one' iff 'mask' == 0. */
    struct hmap_node *one;
    size_t mask;
    size_t n;
};

        可以看到,这里使用桶(bucket)的方式来存储节点,并通过 mask 成员快速确定节点应该被存储在哪个桶中。当哈希表很小时,则会直接使用 one 成员来存储单个节点,避免了维护桶数组带来的开销。

(2)hmap 哈希节点

/* A hash map node, to be embedded inside the data structure being mapped. */
struct hmap_node {
    size_t hash;                /* Hash value. */
    struct hmap_node *next;     /* Next in linked list. */
};

        由此可见,哈希节点中维护了一个单向链表,所有哈希值相同的节点会链接到一个链表中,以此来解决哈希冲突的问题(拉链法)。

        所以这个 hmap 哈希表大概像下面这样:

(3)hmap 哈希表初始化

/* Initializes 'hmap' as an empty hash table. */
void hmap_init(struct hmap *hmap) {
    hmap->buckets = &hmap->one;
    hmap->one = NULL;
    hmap->mask = 0;
    hmap->n = 0;
}

(4)hmap 哈希表插入元素

        快速插入元素:

/* Inserts 'node', with the given 'hash', into 'hmap'.
 * 'hmap' is never expanded automatically. */
static inline void hmap_insert_fast(struct hmap *hmap, struct hmap_node *node, size_t hash) {
    struct hmap_node **bucket = &hmap->buckets[hash & hmap->mask];
    node->hash = hash;
    node->next = *bucket;
    *bucket = node;
    hmap->n++;
}

        正常插入元素:

/* Inserts 'node', with the given 'hash', into 'hmap', and expands 'hmap' if necessary to optimize search performance. */
static inline void hmap_insert_at(struct hmap *hmap, struct hmap_node *node, size_t hash, const char *where) {
    hmap_insert_fast(hmap, node, hash);
    if (hmap->n / 2 > hmap->mask) {
        hmap_expand_at(hmap, where);
    }
}

        二者的区别在于正常插入元素后,还要额外检查当前哈希表是否需要扩容。

(5)hmap 哈希表扩容

/* Expands 'hmap', if necessary, to optimize the performance of searches. */
void hmap_expand_at(struct hmap *hmap, const char *where) {
    size_t new_mask = calc_mask(hmap->n);
    if (new_mask > hmap->mask) {
        COVERAGE_INC(hmap_expand);
        resize(hmap, new_mask, where);
    }
}

        扩容的目的主要是为了避免哈希表过于拥挤,导致查找效率下降。这里扩容的标准是 哈希表中的元素数量超过哈希表大小的一半。具体的扩容操作通过 resize() 函数实现:

static void resize(struct hmap *hmap, size_t new_mask, const char *where) {
    struct hmap tmp;
    size_t i;

    ovs_assert(is_pow2(new_mask + 1));

    hmap_init(&tmp);
    if (new_mask) {
        tmp.buckets = xmalloc(sizeof *tmp.buckets * (new_mask + 1));
        tmp.mask = new_mask;
        for (i = 0; i <= tmp.mask; i++) {
            tmp.buckets[i] = NULL;
        }
    }
    int n_big_buckets = 0;
    int biggest_count = 0;
    int n_biggest_buckets = 0;
    for (i = 0; i <= hmap->mask; i++) {
        struct hmap_node *node, *next;
        int count = 0;
        for (node = hmap->buckets[i]; node; node = next) {
            next = node->next;
            hmap_insert_fast(&tmp, node, node->hash);
            count++;
        }
        if (count > 5) {
            n_big_buckets++;
            if (count > biggest_count) {
                biggest_count = count;
                n_biggest_buckets = 1;
            } else if (count == biggest_count) {
                n_biggest_buckets++;
            }
        }
    }
    hmap_swap(hmap, &tmp);
    hmap_destroy(&tmp);

    if (n_big_buckets) {
        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
        COVERAGE_INC(hmap_pathological);
        VLOG_DBG_RL(&rl, "%s: %d bucket%s with 6+ nodes, "
                    "including %d bucket%s with %d nodes "
                    "(%"PRIuSIZE" nodes total across %"PRIuSIZE" buckets)",
                    where,
                    n_big_buckets, n_big_buckets > 1 ? "s" : "",
                    n_biggest_buckets, n_biggest_buckets > 1 ? "s" : "",
                    biggest_count,
                    hmap->n, hmap->mask + 1);
    }
}

(6)hmap 哈希表删除元素

/* Removes 'node' from 'hmap'.  Does not shrink the hash table;
 * call hmap_shrink() directly if desired. */
static inline void hmap_remove(struct hmap *hmap, struct hmap_node *node) {
    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
    while (*bucket != node) {
        bucket = &(*bucket)->next;
    }
    *bucket = node->next;
    hmap->n--;
}

(7)hmap 哈希表遍历元素:

        常规遍历:

/* Iterates through every node in HMAP. */
#define HMAP_FOR_EACH(NODE, MEMBER, HMAP) \
    HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, (void) 0)
#define HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, ...)                           \
    for (INIT_MULTIVAR_EXP(NODE, MEMBER, hmap_first(HMAP), struct hmap_node,  \
                           __VA_ARGS__);                                      \
         CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL);            \
         UPDATE_MULTIVAR(NODE, hmap_next(HMAP, ITER_VAR(NODE))))

        安全遍历:(允许在遍历的过程中删除节点)        

/* Safe when NODE may be freed (not needed when NODE may be removed from the hash map but its members remain accessible and intact). */
#define HMAP_FOR_EACH_SAFE_LONG(NODE, NEXT, MEMBER, HMAP) \
    HMAP_FOR_EACH_SAFE_LONG_INIT (NODE, NEXT, MEMBER, HMAP, (void) NEXT)

#define HMAP_FOR_EACH_SAFE_LONG_INIT(NODE, NEXT, MEMBER, HMAP, ...)           \
    for (INIT_MULTIVAR_SAFE_LONG_EXP(NODE, NEXT, MEMBER, hmap_first(HMAP),    \
                                     struct hmap_node, __VA_ARGS__);          \
         CONDITION_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER,                     \
                                      ITER_VAR(NODE) != NULL,                 \
                            ITER_VAR(NEXT) = hmap_next(HMAP, ITER_VAR(NODE)), \
                                      ITER_VAR(NEXT) != NULL);                \
         UPDATE_MULTIVAR_SAFE_LONG(NODE, NEXT))

(8)总结

        对于 Open vSwitch 而言 hmap 是最基础的哈希表结构,其他的各种类型的哈希表都是在 hmap 的基础上,增加对新的数据类型的适配,然后相应增加一些新的方法。

        上述内容只列举了一些 hmap 常见的功能实现,但其实 Open vSwitch 中 hmap 实现的功能远不止于此。就我观察发现在 C++ STL 的哈希表 unordered_map 中常用的功能和算法等内容都能在 hmap 的代码中找到相应的实现。

        此外,关于 Open vSwitch 为什么不采用 STL 而是选择自己手搓数据结构(如哈希表)的原因有一些猜想:首先 STL 为了提供更通用的接口,可能会牺牲一些性能,而自己实现的数据结构可以实现定制化,让数据结构更加贴合实际场景,获得更好的性能提升(缺点是代码阅读的难度也会提升);其次 STL 使用的动态内存分配机制,对于某些内存要求严格的场景不够友好,自己实现能够采用更贴近需求的内存管理策略;最后,也有可能是最开始写的大佬不想用(悲),但其实 Open vSwitch 源码的结构还是很清晰的(就是风格稍微有一点点怪),凑活读吧。

结语:

        由于本人水平有限,以上内容如有不足之处欢迎大家指正(评论区/私信均可)。

参考资料:

Open vSwitch 官网

Open vSwitch 源代码 GitHub

Open vSwitch v3.3.0 源代码阅读

浅析 Open vSwitch 数据结构:哈希表 hmap / smap / shash - ovs hash算法-CSDN博客

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
openvswitch是一种用于虚拟交换的开源软件。而ovs-dpdk是openvswitch的一种插件,它提供了使用Data Plane Development Kit(DPDK)的功能来加速虚拟交换。 硬件offload是指将一些计算任务交给硬件来执行,以提高性能和效率。在ovs-dpdk,硬件offload用于加速虚拟交换功能,提供更高的吞吐量和更低的延迟。 通过使用DPDK,ovs-dpdk可以直接与网络接口卡(NIC)进行交互,绕过操作系统内核,减少了数据包在主机上的处理和拷贝操作,提高了数据包的处理速度。 ovs-dpdk的硬件offload功能主要包括以下几个方面: 1. 网络虚拟化加速:ovs-dpdk可以将网络虚拟化的一些关键任务,如虚拟交换机的转发、过滤、隧道封装等,通过硬件加速实现,提高虚拟机之间的通信性能。 2. 动态流表:通过与DPDK和硬件交互,ovs-dpdk可以动态地配置硬件流表,对数据包进行分类和处理,从而减少了软件处理所需的时间和资源。 3. 超大流量处理:ovs-dpdk支持高达数百万个数据包每秒(Mpps)的数据包处理能力,适用于高密度数据心或网络交换机等场景,能够应对大规模流量需求。 总结起来,ovs-dpdk的硬件offload功能能够加速虚拟交换功能,提供更高的性能和效率。通过与DPDK和硬件配合,ovs-dpdk可以实现网络虚拟化加速、动态流表和超大流量处理等功能,满足高性能虚拟交换的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值