基数(radix)树

深入浅出地讲解基数树(Radix Tree) 基数树是一种在特定场景下极其高效的数据结构。它通过路径压缩的思想,以增加实现的复杂度为代价,换取了显著的空间优化。当你处理大量具有共同前缀的字符串数据,并且对内存占用非常敏感时(如网络路由、内核级数据结构),基数树是一个非常值得考虑的强大工具。 阅读详情

基数(radix)树  

Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。


IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。IDR机制的API函数在lib/idr.c中实现,这里不加分析。


Linux radix树最广泛的用途是用于内存管理,结构address_space通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。

radix树概述

radix树是通用的字典类型数据结构,radix树又称为PAT位树(Patricia Trie or crit bit tree)。Linux内核使用了数据类型unsigned long的固定长度输入的版本。每级代表了输入空间固定位数。


radix tree是一种多叉搜索树,树的叶子结点是实际的数据条目。每个结点有一个固定的、2^n指针指向子结点(每个指针称为槽slot),并有一个指针指向父结点。

Linux内核利用radix树在文件内偏移快速定位文件缓存页,图4是一个radix树样例,该radix树的分叉为4(22),树高为4,树的每个叶子结点用来快速定位8位文件内偏移,可以定位4x4x4x4=256页,如:图中虚线对应的两个叶子结点的路径组成值0x00000010和0x11111010,指向文件内相应偏移所对应的缓存页。

图4 一个四叉radix树


Linux radix树每个结点有64个slot,与数据类型long的位数相同,图1显示了一个有3级结点的radix树,每个数据条目(item)可用3个6位的键值(key)进行索引,键值从左到右分别代表第1~3层结点位置。没有孩子的结点在图中不出现。因此,radix树为稀疏树提供了有效的存储,代替固定尺寸数组提供了键值到指针的快速查找。

图1 一个3级结点的radix树及其键值表示

radix树slot数

Linux内核根用户配置将树的slot数定义为4或6,即每个结点有16或64个slot,如图2所示,当树高为1时,64个slot对应64个页,当树高为2时,对应64*64个页。


图2 高为1和2、slot数为64的radix树


Linux内核radix树的slot数定义如下(在lib/radix-tree.c中):


#ifdef __KERNEL__
/*值为6时,表示每个结点有2^6=64个slot,值为4时,表示有2^4=16个slot*/
#define RADIX_TREE_MAP_SHIFT (CONFIG_BASE_SMALL ? 4 : 6)
#else
#define RADIX_TREE_MAP_SHIFT 3 /* 用于更有强度的测试 */
#endif
/*表示1个叶子结点可映射的页数,如:1<<6=64,表示可映射64个slot映射64页*/
#define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT)
#define RADIX_TREE_MAP_MASK (RADIX_TREE_MAP_SIZE-1)
/*定义slot数占用的long类型长度个数,每个slot用位图1位进行标记,如:64个slot时,值为2*/
#define RADIX_TREE_TAG_LONGS \
((RADIX_TREE_MAP_SIZE + BITS_PER_LONG - 1) / BITS_PER_LONG)

结点结构

树的根结点结构radix_tree_root列出如下(在include/linux/radix-tree.h中):
#define RADIX_TREE_MAX_TAGS 2 /*每个slot需要的最大标签位数*/

/*根结点的标签存放在gfp_mask中,通过__GFP_BITS_SHIFT移位得到 */
struct radix_tree_root {
    unsigned int height; /* 从叶子向上计算的树高度 */
    gfp_t gfp_mask;
    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针*/
    struct radix_tree_node *rnode;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    struct radix_tree_context *context;
    struct task_struct *owner;
#endif
};


结构radix_tree_context列出如下:
struct radix_tree_context {
    struct radix_tree_root *root;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    spinlock_t *locked;
#endif
}


树的结点结构radix_tree_node列出如下(在lib/radix-tree.c中):

struct radix_tree_node {
    unsigned int height; /* 从叶子向上计算的树高度 */
    unsigned int count; /*非叶子结点含有一个count域,表示出现在该结点的孩子的数量*/

    struct rcu_head rcu_head;
    void *slots[RADIX_TREE_MAP_SIZE]; /*结点的slot指针*/
   /* 结点标签数组=每个slot需要的最大标签位数*slot数所需的long类型变量数 */
   unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};


Linux内核radix树高度是可变的,它依赖于存储的索引键值。radix树查询必须知道树高度,以便知道结点的slot是指向叶子结点的指针还是结点的其他层。Linux在结点结构中有成员height存入树高度信息。
Linux radix树的独特特征是标签扩展。图2显示了有2个标签,分别表示打开和关闭。这些标签是结点结构的成员tags,该标签用一个位图实现,在加锁的情况下设置和清除。每个标签基本上是一个在radix树顶层上的位图序号。标签与群查询一起用来在给定的范围内查找给定标签集的页。
标签在每结点位图中维护,在每个给定的级别上,可以判定下一个级别是否至少有一个标签集。



图2 带有2位图标签指示的8位radix树


在结点结构radix_tree_node中,tags域是一个二维数组,每个slot用2位标识,这是一个典型的用空间换时间的应用。tags域用于记录该结点下面的子结点有没有相应的标志位。目前RADIX_TREE_MAX_TAGS为2,表示只记录两个标志,其中tags[0]为PAGE_CACHE_DIRTY,tags[1]为PAGE_CACHE_WRITEBACK。如果当前节点的tags[0]值为1,那么它的子树节点就存在PAGE_CACHE_DIRTY节点,否则这个子树分枝就不存在着这样的节点,就不必再查找这个子树了。比如在查找PG_dirty的页面时,就不需要遍历整个树,而可以跳过那些tags[0]为0值的子树,这样就提高了查找效率。
“加标签查询”是返回有指定标签集radix树条目的查询,可以查询设置了标签的条目。加标签查询可以并行执行,但它们需要通过设置或清除标签从操作上互斥。

并行操作的优化


Linux radix树并行操作包括并行查询和并行修改,其中,并行修改在标准内核中未完没有实现,需要通过打补丁获得该功能。并行操作说明如下:

RCU并发查询


通过使用RCU,RCU Radix树可以进行完全并发的查询操作。RCU从根本上要求原子操作地移动指针从数据结构的一个版本到新的版本,保持旧版本直到系统经过静止状态。在静止状态点,旧版本数据结构已没有用户,因此可以被安全释放。


RCU radix树的修改操作之间还需要串行化,但是查询不再需要与修改操作串行化。

并发修改

RCU可使RCU radix树查询完全并行化,但修改操作成了“瓶颈”。这可通过将全树的锁破碎成较小的锁进行改善,再明显的方法是对结点进行加锁而非对整个树加锁。


radix树修改操作可分为单向和双向操作。单向操作仅执行从根节点和叶子结点的单方向指针移动,它包括插入、更新和设置标签操作。双向操作较复杂,它需要在指针移到叶子后又回移,它包括删除和清除标签操作。


梯级加锁(Ladder Locking)和锁耦合(Lock-Coupling)技术常用于数据库方面,允许单向遍历结点加锁的树(双向可能产生死锁)。如果所有的修改者从树顶到树底进行修改,并且修改的结点持有锁,那么,向下遍历时对孩子加锁,在孩子被锁住时再释放该结点锁。在这种情况下并发操作是可能的,因为只要根结点解锁,另一个操作就可以自上向下进行。如果两操作的路径没有相同操作结点,后一个操作可能在前一个操作完成之前完成。最坏的情况是流水线操作,但这还是比串行化操作好很多。


双向操作包括删除和清除标签操作,分别说明如下:


1)清除标签

在radix树中清除一个标签包括向下遍历树、查找定位条目和清除条目标签的操作。只要孩子结点没有打标签的条目,就可以向上遍历结点清除标签。结束条件是:如果遍历遇到一个结点,在清除一个标签后,它还有一个或多个条目带有标签集,就可以结束向上遍历。为了与向下遍历期间有同样的结束点,将终止条件改为:向上遍历将在有比清除标签数更多标签的结点处结束。这样,不论何时遇到这样的结点,将作为上遍历树的结束点。


2)删除元素
删除元素在删除无用结点时还需要删除该条目的所有标签。它的终止条件需要满足这两个方面。向上回退遍历树时需要满足下面的条件:当遇到一个非空结点且没有无用的标签时应终止向上回退遍历树。
在向下遍历树时鉴别此点的条件是:当遇到有超过2个孩子的结点、并且每个标签来说结点有多于一个标签条目被清除时,结束向上遍历。该条件用来鉴别向上回退遍历的终止点。

radix树API说明

声明和初始化radix树

声明和初始化radix树的方法列出如下:


#include <linux/radix-tree.h>
/* gfp_mask表示如何执行内存分配,如果操作(如:插入)以原子性上下文中执行,其值为GFP_ATOMIC*/
RADIX_TREE(name, gfp_mask); /* 声明和初始化名为name的树*/

struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);


插入条目


插入条目的函数定义列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)


函数radix_tree_insert插入条目item到树root中,如果插入条目中内存分配错误,将返回错误-ENOMEM。该函数不能覆盖写正存在的条目。如果索引键值index已存在于树中,返回错误-EEXIST。插入操作成功是,返回0。
对于插入条目操作失败将引起严重问题的场合,下面的一对函数可避免插入操作失败:

int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);


函数radix_tree_preload尝试用给定的gfp_mask分配足够的内存,保证下一个插入操作不会失败。在调用插入操作函数之前调用此函数,分配的结构将存放在每CPU变量中。函数radix_tree_preload操作成功后,将完毕内核抢占。因此,在插入操作完成之后,用户应调用函数radix_tree_preload_end打开内核抢占。


删除条目


删除条目的函数定义列出如下:

void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)


函数radix_tree_delete删除与索引键值index相关的条目,如果删除条目在树中,返回该条目的指针,否则返回NULL。


查询操作


用于查询操作的函数定义列出如下:

/*在树中查找指定键值的条目,查找成功,返回该条目的指针,否则,返回NULL*/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/*返回指向slot的指针,该slot含有指向查找到条目的指针*/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/*查询返回max_items条目在results中。查询时键值索引从first_index开始*/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);


标签操作


与标签操作相关的函数说明列出如下:

/*将键值index对应的条目设置标签tag,返回值为设置标签的条目*/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从键值index对应的条目清除标签tag,返回值为清除标签的条目*/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*检查键值index对应的条目是否为标签tag,如果键值不存在,返回0,如果键值存在,但标签未设置,返回-1;如果键值存在,且标签已设置,返回1*/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从first_index起查询树root中标签值为tag的条目,在results中返回*/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/*如果树root中有任何条目使用tag标签,返回键值*/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);


并行操作使用radix树API的方法

查询获取slot操作


查询操作支持RCU无阻塞并行读操作,因此,需要遵循RCU的用法加RCU读锁,还需要将rcu_dereference()用于获得的slot,在写(或更新)操作时,需要给新的slot使用rcu_assign_pointer()。查询操作的使用方法列出如下:

struct page **slot, *page;
rcu_read_lock();
slot = radix_tree_lookup_slot(&mapping->page_tree, index);
page = rcu_dereference(*slot);
rcu_read_unlock();


查询修改slot操作


Linux内核的radix树需要打补丁才支持并发修改。查询仅有一个全局状态:RCU静止状态,并发修改需要跟踪持有什么锁。锁状态对于操作来说必须是外部的,因此,我们需要实例化一个本地上下文跟踪这些锁。查询修改slot的方法列出如下:

struct page **slot;
DEFINE_RADIX_TREE_CONTEXT(ctx,&mapping->page_tree);
radix_tree_lock(&ctx); /*锁住了根结点*/
/* ctx.tree代替&mapping->page_tree作为根,可以传递上下文
slot = radix_tree_lookup_slot(tx.tree, index);
rcu_assign_pointer(*slot, new_page);
radix_tree_unlock(&ctx);


radix树API函数radix_tree_lookup_slot含有锁从树顶向下移动机制,锁移动的代码部分列出如下:

void **radix_tree_lookup_slot(struct radix_tree *root, unsigned long index)
{
    ...
    RADIX_TREE_CONTEXT(context, root); /*提供上下文和实际的root指针*、
    ...
    do {
        ...
        /* 从树顶向下移动锁*/
        radix_ladder_lock(context, node);
        ...
    } while (height > 0);
    ...
}


radix树API的实现

树的操作通常包括查找、插入、删除和树调整,下面分别说明radix树这些操作的实现。


查找单个条目


radix树通过索引键值进行查询,如图1所示,它按路径组成索引键值,图中3级结点对应3段6位表示的索引值,图中key对应的叶子slot的索引键值为“2,63,1”。通过叶子slot的指针slot[1]就可得到所存储的数据item。因此,查询迭代时,需要将索引键值“2,63,1”移去高2层“2,63”,得到叶子slot的指针索引键值“1”。


图1 一个3级结点的radix树及其键值表示


函数radix_tree_lookup执行查找操作,查找方法是:从叶子到树顶,通过数组索引键值值查看数组元素的方法,一层层地查找slot。其列出如下(在lib/radix-tree.c中):

void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
{
    unsigned int height, shift;
    struct radix_tree_node *node, **slot;

    node = rcu_dereference(root->rnode); /*获取根结点*/
    if (node == NULL)
        return NULL;


    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针。对于间接指针来说,树高度值root->height大于0,但是RCU查找需要测试间接指针,因为root->height 值不可靠。这种问题仅的RCU下需要考虑*/
    if (!radix_tree_is_indirect_ptr(node)) { /*非间接指针,说明只有根结点*/
        if (index > 0)
            return NULL;
        return node;
    }


    /*获取真正结点指针,因为根结点指针的第0位设置为1表示为间接指针。当使用结点指针时,必须将第0位设置为0,因为地址以字对齐*/
    node = radix_tree_indirect_to_ptr(node);

    height = node->height;
    if (index > radix_tree_maxindex(height)) /*索引键值不能超过最大索引值*/
        return NULL;


    /*每层索引偏移值为RADIX_TREE_MAP_SHIFT,叶子索引值偏移基数为(树高-1)*每层索引偏移值*/
    shift = (height-1) * RADIX_TREE_MAP_SHIFT;

    do { /*从叶子到树顶,通过树路径组成的索引查找指定索引键值的slot*/
        slot = (struct radix_tree_node **)(node->slots + ((index>>shift) & RADIX_TREE_MAP_MASK)); /*如:slots +1*/
        node = rcu_dereference(*slot);
        if (node == NULL)
            return NULL;

        shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,再迭代查找*/
        height--;
    } while (height > 0);

   return node;
}


查找多个条目


函数radix_tree_gang_lookup执行多个索引键值的查找,其列出如下:

unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items)
{
    unsigned long max_index;
    struct radix_tree_node *node;
    unsigned long cur_index = first_index;
    unsigned int ret;

    node = rcu_dereference(root->rnode);
    if (!node)
        return 0;

    if (!radix_tree_is_indirect_ptr(node)) { /*如果为非间接指针,表示只有根节点*/
        if (first_index > 0)
            return 0;
        results[0] = node;
        return 1;
    }
    node = radix_tree_indirect_to_ptr(node); /*清除用于间接指针标识的第0位*/

    max_index = radix_tree_maxindex(node->height); /*获取树的最大索引键值*/

    ret = 0;
    while (ret < max_items) { /* max_items为查找的最大条目数*/
        unsigned int nr_found;
        unsigned long next_index; /* 下一个搜索的索引键值*/

        if (cur_index > max_index) /*已查询完所需查询的索引键值*/
            break;
        nr_found = __lookup(node, results + ret, cur_index,
        max_items - ret, &next_index);
        ret += nr_found;
        if (next_index == 0)
            break;
        cur_index = next_index;
    }

    return ret;
}


static unsigned int
__lookup(struct radix_tree_node *slot, void **results, unsigned long index, unsigned int max_items, unsigned long *next_index)
{
    unsigned int nr_found = 0;
    unsigned int shift, height;
    unsigned long i;

    height = slot->height;
    if (height == 0)
        goto out;
    /*所有叶子slot的索引键值基数偏移*/
    shift = (height-1) * RADIX_TREE_MAP_SHIFT;
    /*从底层向树顶层,
    for ( ; height > 1; height--) { /*从叶子向树顶查找*/
        i = (index >> shift) & RADIX_TREE_MAP_MASK;
        for (;;) { /*遍历每一层的各个路径,由树顶到当前层一条路径组成索引键值*/
            /*如果slot不为空,那么它挂有子结点,跳出本循环,进入子结点层进行本循环*/
            if (slot->slots[i] != NULL)
                break;
           /*如果slot为空,就跳过slot对应的所有索引键值*/
           /*清除索引号低位.将索引号与该层slot的起始索引号对齐*/
           index &= ~((1UL << shift) - 1);
           /*跳过一个slot的索引键值数*/
          index += 1UL << shift;
          if (index == 0)
              goto out; /* 32-bit wraparound */
          i++; /*找到多个slot*/
          if (i == RADIX_TREE_MAP_SIZE)
              goto out;
       }

       shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,基数偏移减少*/
       slot = rcu_dereference(slot->slots[i]);
       if (slot == NULL)
           goto out;

    }

    /* 返回找到的多个slot*/
    for (i = index & RADIX_TREE_MAP_MASK; i < RADIX_TREE_MAP_SIZE; i++) {
        struct radix_tree_node *node;
        index++;
        node = slot->slots[i];
        if (node) {
            results[nr_found++] = rcu_dereference(node);
           if (nr_found == max_items)
               goto out;
        }
    }
    out:
    *next_index = index;
    return nr_found;
}


插入条目


函数radix_tree_insert找到索引键值对应的结点,将item加到该结点的slot指针上。其列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)
{
    struct radix_tree_node *node = NULL, *slot;
    unsigned int height, shift;
    int offset;
    int error;

    BUG_ON(radix_tree_is_indirect_ptr(item));

    /* 如果树的高度不够,就扩展树。函数radix_tree_maxindex计算树高容纳的最大索引*/
    if (index > radix_tree_maxindex(root->height)) {
        error = radix_tree_extend(root, index);
        if (error)
            return error;
    }

    slot = radix_tree_indirect_to_ptr(root->rnode);

    height = root->height;
    shift = (height-1) * RADIX_TREE_MAP_SHIFT; /*计算偏移基数*/

    offset = 0;
    while (height > 0) {
        if (slot == NULL) { /*如果slot为空,则需要加入孩子结点*/
            /* 分配slot */
            if (!(slot = radix_tree_node_alloc(root)))
                return -ENOMEM;
            slot->height = height;
            if (node) {/*添加slot*/
                rcu_assign_pointer(node->slots[offset], slot);
             node->count++;
         } else
             rcu_assign_pointer(root->rnode,radix_tree_ptr_to_indirect(slot));
        }

        /* 进入上一层*/
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        node = slot;
        slot = node->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    }
    /*如果index对应的slot已有映射页面,返回-EEXIST*/
    if (slot != NULL)
        return -EEXIST;

    if (node) {
        node->count++; /*增加子结点的计数*/
        rcu_assign_pointer(node->slots[offset], item);
        BUG_ON(tag_get(node, 0, offset));
        BUG_ON(tag_get(node, 1, offset));
    } else { /*为顶层结点*/
        rcu_assign_pointer(root->rnode, item);
        BUG_ON(root_tag_get(root, 0));
        BUG_ON(root_tag_get(root, 1));
    }

    return 0;
}


扩展树的高度


如果当前树高度不足以存放index,就需要扩展树,扩展方法是在旧树顶上加新的根结点,并将原根结点的tag信息移到新根结点的第1个slot。函数radix_tree_extend 列出如下:
static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
{
    struct radix_tree_node *node;
    unsigned int height;
    int tag;

    /* 计算扩展的深度*/
    height = root->height + 1;
    /*如果index超过树高所容纳的最大索引值,树高递增*/
    while (index > radix_tree_maxindex(height)) 
        height++;

    /*到这里已计算好合适的高度height*/
    if (root->rnode == NULL) { /*只有根结点,设置好树高度就可返回*/
        root->height = height;
        goto out;
    }

    /*将当前树扩展到高度height*/
    do {
        unsigned int newheight;
        if (!(node = radix_tree_node_alloc(root))) /*分配一个结点*/
            return -ENOMEM;

        /* 增加树高,在树顶点之上增加一个结点*/
        node->slots[0] = radix_tree_indirect_to_ptr(root->rnode);

        /*传播tag信息到新根结点*/
        /*以前的根结点现成为新顶结点的第1个插槽。 如果以前的根结点打上了tag,就将新增结点的第1个插槽对应的子节点打上相应的tag*/
        for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
            if (root_tag_get(root, tag))
               tag_set(node, tag, 0);
        }

        newheight = root->height+1;
        node->height = newheight;
        node->count = 1;
        node = radix_tree_ptr_to_indirect(node);
        rcu_assign_pointer(root->rnode, node);
        root->height = newheight;
    } while (height > root->height);
out:
    return 0;
}


删除条目


函数radix_tree_delete删除index对应的条目,并返回删除条目的地址。其列出如下:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
{
    /*数组path用于保存路径上的结点及索引偏移值*/
    struct radix_tree_path path[RADIX_TREE_MAX_PATH + 1], *pathp = path;
    struct radix_tree_node *slot = NULL;
    struct radix_tree_node *to_free;
    unsigned int height, shift;
    int tag;
    int offset;

    height = root->height;
    if (index > radix_tree_maxindex(height)) /*index不能超过树的最大索引值*/
        goto out;

    slot = root->rnode;
    if (height == 0) { /*只有根结点*/
        root_tag_clear_all(root);
        root->rnode = NULL;
        goto out;
    }
    slot = radix_tree_indirect_to_ptr(slot);

    shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
    pathp->node = NULL;
    /*获取删除条目的路径*/
    /*将index从根到叶子的路径所经过的结点及相应索引偏移值存放在数组pathp中*/
    do {
        if (slot == NULL)
            goto out;

        pathp++;
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        pathp->offset = offset;
        pathp->node = slot;
        slot = slot->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    } while (height > 0);

    if (slot == NULL)
        goto out;

    /*清除与删除条目相关的所有标签*/
    for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
        if (tag_get(pathp->node, tag, pathp->offset))
            radix_tree_tag_clear(root, index, tag);
    }

    to_free = NULL;
    /*释放不再需要的结点 */
    while (pathp->node) { /*删除
        pathp->node->slots[pathp->offset] = NULL; /*清除slot*/
        pathp->node->count--; /*孩子数量减1*/
        /*调用RCU机制的函数call_rcu在最后一个引用结束时延迟释放结点to_free */
        if (to_free)
            radix_tree_node_free(to_free);

        if (pathp->node->count) { /*还有孩子存在*/
           if (pathp->node == radix_tree_indirect_to_ptr(root->rnode)) /*为根结点的孩子*/
                radix_tree_shrink(root); /*树缩小*/
           goto out;
        }

        /*释放有0个slot的结点 */
        to_free = pathp->node;
        pathp--;

    }
    /*运行到这里,说明是根结点*/
    root_tag_clear_all(root);
    root->height = 0;
    root->rnode = NULL;
    if (to_free)
        radix_tree_node_free(to_free);

out:
    return slot;
}


缩小树的高度


函数radix_tree_shrink缩小树的高度到最小。其列出如下:

static inline void radix_tree_shrink(struct radix_tree_root *root)
{
    /* 尝试缩小树的高度*/
    while (root->height > 0) {
        struct radix_tree_node *to_free = root->rnode;
        void *newptr;

        BUG_ON(!radix_tree_is_indirect_ptr(to_free));
        to_free = radix_tree_indirect_to_ptr(to_free);

        /*候选结点多于一个孩子或孩子不在最左边slot,不能缩小树的高度,跳出循环*/
        if (to_free->count != 1)
            break;
        if (!to_free->slots[0])
            break;

        /*不需要调用rcu_assign_pointer(),因为仅从树的一部分到另一部分移动结点。如果释放旧指针的引用to_free->slots[0]是安全的,那么释放新指针的引用root->rnode也是安全的*/
       newptr = to_free->slots[0];
       if (root->height > 1)
           newptr = radix_tree_ptr_to_indirect(newptr);
       root->rnode = newptr;
       root->height--; 
       /* 仅释放0结点*/
       tag_clear(to_free, 0, 0);
       tag_clear(to_free, 1, 0);
       to_free->slots[0] = NULL;
       to_free->count = 0;
       radix_tree_node_free(to_free);
    }
}
数据结构之Radix和Trie Radix和Trie 阅读详情

相关推荐

第十八章 Radix树路由表

一、 linux的路由表结构: 一共六列,分别是: 目的IP地址; 网关; 标识量:包括U G H S C L R; 引用量; 使用量,应该是通过该IP地址发送的数据量; 发往IP地址的数据包的出口接口名称。 二、 路由表查表方法: 总共五个步骤: 路由表是一个树形结构,按照比特比较+掩码比较+回溯组成。 首先,从linux地址结构的包含IP地址的第一位开始,也就是比特32的地方进行比较,按

david的博客 1899

图解基数树(RadixTree)

基数树(RadixTree),是一种比较有趣的数据结构,最近需要一种比较高效的查找,两度遇到了基数树,便整理下来给有相关需求的伙伴提供一种思路。

qq_41583040的博客 2124

python零基础入门到精通,python入门到精通教程

坚持到了这儿,恭喜你,表示你有做开发的潜力,其实我想说的上面的内容还是刚刚开始,刚开始大家不需要多么精通了解这些内容,除了Python方面的知识,每个部分掌握一点儿能进行基本开发就好,主要是不断练习,让自己跳出「舒适区」,进入「学习区」,但是又不进入「恐慌区」,不断给自己「喂招」。上述这份完整版的Python全套学习资料已经上传优快云官方,朋友们如果需要可以扫描下方二维码免费获取【保证100%免费】

YYmmsss的博客 3435

数据结构之基数树(radix_tree)

数据结构之基数树(radix_tree)基数树(radix_tree)一,基数树数据的结构的介绍二, 基数树应用场景介绍三,基数树实现总结: 基数树(radix_tree) 基数树数据结构是在字典树(trie_tree)的原理上优化过来的, 是更加合理的使用内存和查询的效率。 一,基数树数据的结构的介绍 基数树数据结构 二, 基数树应用场景介绍 三,基数树实现 总结: ...

永远积极向上、永远热泪盈眶、永远豪情满怀、永远坦坦荡荡 4188

电磁场可视化

电磁场可视化 如题,做了电磁场可视化这么久了,也是颇有些心得了,简单描述一下怎么做这个项目的。 开始时是从最基本的OpenGL开始做的,必不可少的翻了许多的文献,后来是直接在unity中做,发现效果挺好,而且可控性强好多。反复琢磨后,发现可视化的渲染,有几种思路,第一就是用linerender,画线的方法,另一种就是用shader。 第一种我通过unity自带的linewave插件可以做到 详细的就是,linewave插件可以轻松画出曲线等,并且可以添加碰撞,控制频率幅度长度等一切的条件数值,而且令人惊喜

weixin_43979140的博客 1670

UE5.5使用ControlRig实现MetaHumanNPC看向玩家

本文介绍了在虚幻引擎中实现NPC自然看向玩家的技术方案。通过MetaHuman的ControlRig系统,采用面部动画曲线+全身动画Montage的表演方式,结合ControlRig驱动CTRL_lookat控制器实现视线追踪。

平常心 1302

Linux 基数(radix)树

基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。 IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用

xiaofeng_yan的专栏 7023

DDR5内存命令真值表详解:如何正确解析JESD79-5标准中的关键指令

本文深度解析JESD79-5标准中的DDR5内存命令真值表,将其视为动态通信协议进行拆解。详细阐述了1-Cycle与2-Cycle命令的区别、Bank Group寻址机制以及模式寄存器(MR)操作流程,为硬件与固件工程师提供了从信号解析到系统调试的完整实战指南,是理解和优化高性能计算平台内存子系统的核心。

numpy6sculptor的博客 548

字典树/trie树,基数树/radix树

转自:百度百科:http://baike.baidu.com/view/2759664.htm

tz_sz的专栏 7057

SuperMap iObjects Java组件开发——环境部署(IntelliJ IDEA)

SuperMap iObjects Java 是面向GIS应用系统开发者的组件式GIS开发平台,具有强大的地理数据管理、编辑、显示、分析等功能,并且其具有极强的易用性和丰富的帮助资源,适宜快速开发大型GIS应用系统。本文将介绍如何在IntelliJ IDEA集成开发环境中进行SuperMap iObjects Java组件的环境部署。

nan620403的博客 1081

通达信浮筹多空波段抓牛擒龙指标公式源码

VAR33:=VA3>=REF(VA4,1) AND C<REF(C,1) AND VAR19=60 AND VAR18=0 AND 活跃线>=REF(活跃线,1) AND VA12<REF(VA13,1);DRAWTEXT(VAR28 AND COUNT(VAR28,30)=1,65,' ★超涨注意风险'),COLOR0000FF;DRAWTEXT(VAR26 AND COUNT(VAR26,30)=1,90,' ←继续等'),COLOR00FFFF;浮筹线:VA3,COLORFF00FF;

2401_86043146的博客 5259

java里面有radix树吗_基数树(radix tree)

基数(radix)树Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID...

weixin_35867815的博客 494

【Autoware】2小时安装Autoware1.13(保姆级教程)

Autoware是一款“一体化”开源自动驾驶软件,能实现感知、决策、控制等功能,通过在Ubuntu中搭建Autoware开发环境和案例的运行,使大家对自动驾驶技术的实现有一个更清晰的认识。

DevFrank的博客 1万+

基数(radix)树 2

基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。 IDR(IDRadix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速

ganggexiongqi的专栏 1360

TC3XX - MCAL知识点(九):PWM EB-tresos配置说明及代码浅析

脉冲宽度调制(PWM)驱动器负责提供与AUTOSAR指定的PWM信号生成相关的标准服务。PWM通道的底层定时器驱动是GTM (TOM或ATOM片)或CCU6 (T12或T13片)定时器通道。软硬件匹配关系如下:PWM直接引用的是GTM与CCU6模块,然后GTM与CCU6再关联到Port的,根据软硬件匹配关系,也能调用中断来使用,本例子没使用中断。CCU6是一个高分辨率的16位捕获和比较单元,具有特定的应用模式,主要用于交流驱动控制。特殊的工作模式支持控制无刷直流电机使用霍尔传感器或反电动势检测。

梦想技术家 2886

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

java里面有radix树吗_radix tree基数树实现 (山寨nginx风格)

#include#include#includeusingnamespacestd;typedefstructradix_node_sradix_node_t;structradix_node_s{radix_node_t*left;radix_node_t*right;radix_node_t*parent;stringkey;string...

weixin_34236672的博客 284

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

基数树(radix tree)与前缀树(Trie)

在读gin路由原理时了解到gin 路由应用数据结构基数树实现,有低内存、高效率的特点。 在此记录一下基数树的原理,同时也对比一下很相近的前缀树。 基数树: 前缀树: 引用 https://en.wikipedia.org/wiki/Radix_treehttps://en.wikipedia.org/wiki/Trie ...

shasha6的专栏 2193

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

golang的路由数据结构-基数树radix

Radix树 Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构。与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩。同样的,Radix树的插入、查询、删除操作的时间复杂度都为O(k)。 Radix树特点 一般由根节点、中间节点和叶子节点组成。 每个节点可以包含一个或多个字符。 树的叶子结点数即是数据条目数。 从根节点到某一节点经过路径的字符连起来即为该节点对应的字符串。 每个节点的所有子节点字符串都不相同。 插入操作 对r

phpduang的博客 962

数据结构之radix(基数)树

分为中间节点和叶子节点,每个节点都可存n(一般为2的m次方)个slot(槽位)(也称分叉个数),叶子节点中每个slot指向真正存的信息(比如:文件管理中按8k Byte切分的偏移,内存管理中按1Byte切分的地址),然后将数字按m拆分一段一段从高到低(或者从低到高,或从中间某段开始到某段结束)拆分。然后结合实际业务,再来定义树高。 比如:64位按6位拆分,则 m=6,n=2的6次方。 49-64(此高位区用不上,先不管) 13-48(共36位,按6位一段段拆分,共拆分成6段(树高为6)) 0

windfree的专栏 603

基数(radix)树 - 嵌入式系统书撰写 (转载)

基数(radix)树 Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。 IDR(IDRadix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速

fivedoumi的专栏 1318

Linux内核数据结构:基数树Radix Tree

基数树是一种将指针与长整数键值相关联的机制,存储有效率且可快速查询,常用于指针与整数值的映射、内存管理等。它是一种多叉搜索树,叶子结点是实际的数据条目,每个结点有固定数量的指针指向子结点,并有一个指针指向父结点。基数树,也被称为 PAT 位树(Patricia Trie or crit bit tree),是通用的字典类型数据结构。Linux 内核使用了数据类型 unsigned long 的固定长度输入的版本,每级代表了输入空间固定位数。在 Linux 内核中,基数树有广泛的用途,比如用于内存管理。

深度Linux 1463

Trie & Radix Tree

字典树与基数树

Deep_C的博客 519

014-数据结构-树形结构-基数树、Patricia树、默克尔树、梅克尔帕特里夏树( Merkle Patricia Tree, MPT)...

一、基数树   Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构。与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩。同样的,Radix树的插入、查询、删除操作的时间复杂度都为O(k)。 1.1、Radix树特点   一般由根节点、中间节点和叶子节点组成。  每个节点可以包含一个或多个字符。  树的叶子结点数即是...

dilv4062的博客 1518

基数树(radix tree)

作用 基数树(radix tree)是将long整数与指针键值相关联的机制,它存储有效率,并且可快速查询,用于整数值与指针的映射,对于长整型数据的映射,如何解决Hash冲突和Hash表大小的设计是一个很头疼的问题,利用radix树可以根据一个长整型(比如一个长ID)快速查找到其对应的对象指针。这比用hash映射来的简单,也更节省空间,使用hash映射hash函数难以设计,不恰当的hash函数可能增大

weixin_36145588的博客 7893

go语言简单实现 前缀树(Trie)与了解基数树(Radix-Tree)

目录结构 Trie Trie.go main.go Trie 的实现 实现写法, 可以参照模仿 container/list 双向链表的写法实现 下面代码中使用的是 slice 存储子节点, 线性查找遍历, 后面会进行 hash优化。 package Trie // 字典树: /* 1. 单词结尾表示:需要使用一个 bool 类型,辨识当前节点是否是结尾字符 叶子结点是 单词结尾 非叶子节点 也可能是单词的结尾 2. 实现功能 增加单词 搜索单词 */ // 定义树的节点

pythonstrat的博客 1879
上一篇: epoll分析
下一篇: 硬盘录像机-DVR文件系统及硬盘发展趋势
linuxcc2011
博客等级 码龄16年 2粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值