TAILQ
隊列是FreeBSD
內核中的一種隊列數據結構,在一些著名的開源庫中(如DPDK
,libevent
)有廣泛的應用。
TAILQ
隊列的定義
TAILQ
隊列有HEAD
和ENTRY
兩種基本的數據結構
#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;
這和Linux
中list
的組織方式不一樣,後者是單純地將struct list_head
作爲鏈表的一個掛接點,並沒有用戶的信息,具體差別可以看下圖:
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_prev
和tqh_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_HEAD
和FAKE_TAILQ_HEAD
(注意其中的紅線和綠線的區別)
如果我們想要刪除隊列的任意一個元素,對FAKE_TAILQ
,我們需要特殊處理該元素是第一個元素的情況(第一個元素的tqe_prev
指針爲空),而TAILQ
就沒有這個煩惱!
TAILQ
隊列的遍歷性能
Linux
中的list
只將struct list_head
作爲用戶元素的掛接點,因此在正向遍歷鏈表時,需要使用container_of
這類接口才能獲取用戶的數據,而TAILQ
由於tqe_next
指針直接指向用戶元素的類型,所以理論上,正向遍歷TAILQ
比list
更快.但逆向遍歷時,由於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;
}