TAILQ 之一二事

logo

TAILQ隊列是FreeBSD內核中的一種隊列數據結構,在一些著名的開源庫中(如DPDK,libevent)有廣泛的應用。

TAILQ隊列的定義

TAILQ隊列有HEADENTRY兩種基本的數據結構

#define	TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type **tqh_last;	/* addr of last next element */		\
}

#define TAILQ_ENTRY(type)                                            \
struct {                                                             \
    struct type *tqe_next;  /* next element */                       \
    struct type **tqe_prev;/* addr of previous next element*/        \
}   

注意:數據結構中的filed都是type類型的指針(或者是二級指針),這裏的type是用戶的隊列元素類型,,將ENTRY結構內嵌到用戶的QUEUE_ITEM結構中:

struct QUEUE_ITEM{  
    int value;  
    TAILQ_ENTRY(QUEUE_ITEM) entries;  
};  

TAILQ_HEAD(headname,QUEUE_ITEM) queue_head; 

這和Linuxlist的組織方式不一樣,後者是單純地將struct list_head作爲鏈表的一個掛接點,並沒有用戶的信息,具體差別可以看下圖:
vs-linux

TAILQ隊列的操作

TAILQ提供了多種操作隊列的API,比如:

TAILQ_HEAD(name, type)
TAILQ_ENTRY(type)
TAILQ_EMPTY(head)
TAILQ_FIRST(head)
TAILQ_FOREACH(var, head, field)	
TAILQ_INIT(head)
TAILQ_INSERT_AFTER(head, listelm, elm, field)
TAILQ_INSERT_BEFORE(listelm, elm, field)
TAILQ_INSERT_TAIL(head, elm, field)
.....

這些接口的實現和更多的操作接口可以參考 FreeBSD queue

TAILQ隊列中爲什麼tqh_prevtqh_last要使用二級指針

要搞清楚這個問題,我們可以考慮如果不使用二級指針會怎麼樣? 就像定義成下面這樣。

#define	FAKE_TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type *tqh_last;	/* last element */		\
}

#define FAKE_TAILQ_ENTRY(type)                                            \
struct {                                                             \
    struct type *tqe_next;  /* next element */                       \
    struct type *tqe_prev;  /*   previous element*/        \
}   

對比一下TAILQ_HEADFAKE_TAILQ_HEAD (注意其中的紅線和綠線的區別)

pic

如果我們想要刪除隊列的任意一個元素,對FAKE_TAILQ,我們需要特殊處理該元素是第一個元素的情況(第一個元素的tqe_prev指針爲空),而TAILQ就沒有這個煩惱!

TAILQ隊列的遍歷性能

Linux中的list只將struct list_head作爲用戶元素的掛接點,因此在正向遍歷鏈表時,需要使用container_of這類接口才能獲取用戶的數據,而TAILQ由於tqe_next指針直接指向用戶元素的類型,所以理論上,正向遍歷TAILQlist更快.但逆向遍歷時,由於TAILQ的取用prev元素的操作比next麻煩的多,因此逆向遍歷是比正向慢的:

#define	TAILQ_PREV(elm, headname, field)				\
	(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))

以下是用附件中的代碼測試的結果:

遍歷TAILQ:

TAILQ traversal time is 31955 us
TAILQ reverse traversal time is 38699 us

遍歷list

list traversal time is 33062 us
list list traversal time is 35864 us

附錄

測試代碼 bsd.c

#include <stdio.h>
#include <stdlib.h> 
#include <sys/time.h>

#define TAILQ_ENTRY(type)                                            \
struct {                                                             \
    struct type *tqe_next;  /* next element */                       \
    struct type **tqe_prev;/* addr of previous next element*/        \
}   

#define	TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type **tqh_last;	/* addr of last next element */		\
}

#define	TAILQ_FIRST(head)	((head)->tqh_first)
#define	TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define	TAILQ_PREV(elm, headname, field)				\
	(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
	
#define	TAILQ_LAST(head, headname)					\
	(*(((struct headname *)((head)->tqh_last))->tqh_last))
	
	
#define	TAILQ_INIT(head) do {						\
	TAILQ_FIRST((head)) = NULL;					\
	(head)->tqh_last = &TAILQ_FIRST((head));			\
} while (0)

#define TAILQ_INSERT_TAIL(head, elm, field) do {			\
	TAILQ_NEXT((elm), field) = NULL;				\
	(elm)->field.tqe_prev = (head)->tqh_last;			\
	*(head)->tqh_last = (elm);					\
	(head)->tqh_last = &TAILQ_NEXT((elm), field);			\
} while (0)

#define	TAILQ_INSERT_BEFORE(listelm, elm, field) do {			\
	(elm)->field.tqe_prev = (listelm)->field.tqe_prev;		\
	TAILQ_NEXT((elm), field) = (listelm);				\
	*(listelm)->field.tqe_prev = (elm);				\
	(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field);		\
} while (0)

#define	TAILQ_FOREACH(var, head, field)					\
	for ((var) = TAILQ_FIRST((head));				\
	    (var);							\
	    (var) = TAILQ_NEXT((var), field))

#define	TAILQ_FOREACH_REVERSE(var, head, headname, field)		\
	for ((var) = TAILQ_LAST((head), headname);			\
	    (var);							\
	    (var) = TAILQ_PREV((var), headname, field))
		
struct QUEUE_ITEM{  
    int value;  
    TAILQ_ENTRY(QUEUE_ITEM) entries;  
};  
TAILQ_HEAD(headname,QUEUE_ITEM) queue_head;  

#define ITEM_NUM 5000000
#define TRAVERSAL 20

int main(int argc,char **argv){  
    struct QUEUE_ITEM *item;   
	long long totaltime = 0;
	struct timeval start,end;
    long long metric[TRAVERSAL];
	int i = 0;
    
	TAILQ_INIT(&queue_head);  
    for(i=1;i<ITEM_NUM;i+=1){  
        item=malloc(sizeof(struct QUEUE_ITEM));  
        item->value=i;  
        TAILQ_INSERT_TAIL(&queue_head, item, entries);  
    }  
    
    for (i = 0; i < TRAVERSAL; i++)
    {
        gettimeofday(&start,NULL);
        TAILQ_FOREACH(item, &queue_head, entries)
        {
            item->value++;
        }   
        gettimeofday(&end,NULL);
        metric[i] = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); // get the run time by microsecond
    }
   
    totaltime = 0;
    for (i=0;i<TRAVERSAL;i++)
    {
		totaltime += metric[i];
    }

    printf("TAILQ traversal time is %lld us\n", totaltime/TRAVERSAL);

	for (i = 0; i < TRAVERSAL; i++)
    {
        gettimeofday(&start,NULL);
        TAILQ_FOREACH_REVERSE(item, &queue_head, headname,entries)
        {
            item->value++;
        }   
        gettimeofday(&end,NULL);
        metric[i] = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); // get the run time by microsecond
    }
	
	totaltime = 0;
    for (i=0;i<TRAVERSAL;i++)
    {
		totaltime += metric[i];
    }
	
	printf("TAILQ reverse traversal time is %lld us\n", totaltime/TRAVERSAL);
    return 0; 
}  

測試代碼 list.c

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>	/* for offsetof */
#include <sys/time.h>

#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})


#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)


#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)

#define list_last_entry(ptr, type, member) \
	list_entry((ptr)->prev, type, member)

#define list_next_entry(pos, member) \
	list_entry((pos)->member.next, typeof(*(pos)), member)

#define list_prev_entry(pos, member) \
	list_entry((pos)->member.prev, typeof(*(pos)), member)
	
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

#define list_for_each_entry_reverse(pos, head, member)			\
	for (pos = list_last_entry(head, typeof(*pos), member);		\
	     &pos->member != (head); 					\
	     pos = list_prev_entry(pos, member))
		 
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

struct list_head {
	struct list_head *next, *prev;
};
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	prev->next = new;
}

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

struct QUEUE_ITEM{
    int value;
    struct list_head node;
};

LIST_HEAD(queue_head);

#define ITEM_NUM 5000000
#define TRAVERSAL 20

int main()
{
    int i = 0;
    struct QUEUE_ITEM *item;
	long long totaltime = 0;
    struct timeval start,end;
    long long metric[TRAVERSAL];
	
	for(i=1;i<ITEM_NUM;i+=1){
        item=malloc(sizeof(struct QUEUE_ITEM));
        item->value = i;
		INIT_LIST_HEAD(&item->node);
        list_add(&item->node, &queue_head);
    }
	
	for (i = 0; i < TRAVERSAL; i++)
    {
        gettimeofday(&start,NULL);
        list_for_each_entry_reverse(item, &queue_head, node)
        {
            item->value++;
        }   

        gettimeofday(&end,NULL);
        metric[i] = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); // get the run time by microsecond
	}
   
    totaltime = 0;
    for (i=0;i<TRAVERSAL;i++)
    {
		totaltime += metric[i];
    }

    printf("list traversal time is %lld us\n", totaltime/TRAVERSAL);
	
    for (i = 0; i < TRAVERSAL; i++)
    {
        gettimeofday(&start,NULL);
        list_for_each_entry(item, &queue_head, node)
        {
            item->value++;
        }   

        gettimeofday(&end,NULL);
        metric[i] = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); // get the run time by microsecond
    }
   
    totaltime = 0;
    for (i=0;i<TRAVERSAL;i++)
    {
		totaltime += metric[i];
    }

    printf("list list traversal time is %lld us\n", totaltime/TRAVERSAL);

	return 0;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章