簡談一下時間輪(Time Wheel)

2021-07-16 10:33:26 字數 3748 閱讀 8854

**

如果乙個程式設計師不知道 time wheel,那麼那個程式設計師一定不是個合格的程式設計師。

timer對於作業系統還是乙個虛擬機器語言或大型中介軟體都起著重要的作用,同時timer演算法的選擇也直接影響著效能。

time wheel翻譯為時間輪,是用於實現定時器timer的經典演算法,演算法細節就不多說了,這裡主要是看看erlang中和linux kernel的time wheel實現有哪些不同。

erlang中的time wheel實現檔案是time.c,kernel中的實現檔案是timer.c,好了,先看看kernel中的實現吧!

linux kernel中的time wheel這麼多年一直沒怎麼改變,主要特點是以下幾點:

1)kernel中的timer是在softirq中執行

2)多cpu同時執行,和process差不多,timer也可在cpu中migrate

3)使用percpu

4) 核心資料結構:

view plain

struct

tvec_base  ____cacheline_aligned;  

這裡的base屬於percpu資料,即每個cpu擁有乙個base,這樣每個cpu執行自己base裡面的timer。這裡有tv1/tv2/tv3/tv4/tv5,這幾個vector維護著所有timer,每次加timer時根據timeout的時間分別加入到不同的vector中,tv1是最近的,tv5是最遠的,kernel首先會在tv1中遍歷timeout的timer,如果遍歷完tv1,則從tv2中的timer list加到tv1中,如果tv2中的timer list用完後,再從tv3中取,注意tv3中的timer可以分布到tv1和tv2中,以此類推,實現**如下:

view plain

#define index(n) ((base->timer_jiffies >> (tvr_bits + (n) * tvn_bits)) & tvn_mask)

/*** __run_timers - run all expired timers (if any) on this cpu.

* @base: the timer vector to be processed.

** this function cascades all vectors and executes all expired timer

* vectors.

*/static

inline

void

__run_timers(

struct

tvec_base *base)  

}  base->running_timer = null;  

spin_unlock_irq(&base->lock);  

}  static

intcascade(

struct

tvec_base *base, 

struct

tvec *tv, 

intindex)  

return

index;  

}    

可以看出,即使你加了很多長時間的timer,kernel的timer效能並沒有減少,因為長時間time被分布到不同的vector中,因此linux kernel中的time wheel演算法適合大容量的timer應用場景。

(注意kernel中每個base的lock用的是spin lock,而不是mutex,下面會講到)

下面再來看看erlang中的timer實現,erlang普遍應用於併發量比較高的場景,erlang的process通訊是通過message,message的傳送接收顯然離不開timer,erlang甚至把timer提公升到語言語法的層次,從此可看出timer在

erlang中使用是多麼的廣泛。

和linux kernel的time wheel比較,erlang有以下幾點不同:

1)erlang的timer執行過程是在erlang process schedule時發生,而不是像很多中介軟體timer實現那樣用單獨的執行緒,這是有歷史原因的(erlang應兼顧到plain cpu的情形)。

2)erlang的scheduler執行緒可以有多個,所以timer wheel需要lock的支援

3)沒有percpu,由於erlang在user space,所以percpu是個很難的問題,原因是搶占的問題,kernel實現的percpu可以顯著提高效能,但也是有代價的,代價就是在很多percpu的處理過程中要關閉搶占,這也就是為什麼rt kernel的人比較頭疼percpu的原因。而在使用者空間,搶占被作業系統強制執行,導致使用者空間程式無法使用percpu。

4)erlang中time wheel沒有像linux kernel那樣把timeout根據相對時間掛載到tv1/tv2/tv3/tv4/tv5中,但是erlang中的wheel slot卻比較大(kernel中的slot是16或64),可以是8192或65536,這在一定程度上緩解了大量長時間timer對效能帶來的影響,如果把 每個wheel的slot的間隔時間算作是1ms,wheel算作8192,那麼幾乎是8s乙個wheel就遍歷完,如果程式中有大量的timer超時時間大於8s,那麼那些timer就會對8192取模掛載在相應的slot下,這就意味著每次遍歷是會有很多並未超時的timer被訪問到,而這在linux kernel中則不存在。核心**如下:

view plain

static

erts_inline 

void

bump_timer_internal(erts_short_time_t dt) 

/* pre: tiw_lock is write-locked */

/* if do_time > tiw_size we want to go around just once */

count = (uint)(dtime / tiw_size) + 1;  

keep_pos = (tiw_pos + dtime) % tiw_size;  

if(dtime > tiw_size) dtime = tiw_size;  

timeout_head = null;  

timeout_tail = &timeout_head;  

while

(dtime > 0)   

/* remove from list */

remove_timer(p);  

*timeout_tail = p;      /* insert in timeout queue */

timeout_tail = &p->next;  

}  else

}  tiw_pos = (tiw_pos + 1) % tiw_size;  

dtime--;  

}  tiw_pos = keep_pos;  

if(tiw_min_ptr)  

tiw_min -= dt;  

erts_smp_mtx_unlock(&tiw_lock);  

綜上比較,在面對大容量timer的情況下linux kernel的time wheel演算法會比erlang更有效率一些。最後還有一點要注意,erlang的time wheel使用的lock是mutex(上面說過linux kernel使用spin lock),在這裡那種lock會更適合time wheel呢?個人覺得spin lock會好些,畢竟臨界區**處理應該會很快。當然如果erlang中ethread mutex使用的是mutex spin機制(mutex使用的是futex,在進入kernel futex前,進行spin lock很短一段時間),那就無所謂了。

談一下稀疏陣列

對於乙個初窺資料結構的人來說,稀疏陣列確實可以很好的幫助你鍛鍊思維。但自從第三次科技革命後,人們都一直在做著用空間去換取時間的損事,而以時間換空間的稀疏陣列,倒也跟北大考古專業有些心心相惜。當乙個陣列中大部分元素為0,或者為同乙個值時,可以使用稀疏陣列來儲存該陣列 小規模陣列便是稀疏陣列 像在編寫的...

談一下多執行緒下的count

在單執行緒下,我們經常使用count 將count的值自增1,也不會發生什麼錯誤,但是在多執行緒下,可能使用count 結果可能就往往出乎我們的意料了.我們以count 一直從1加到5為例 public class threadcount extends thread override public...

回顧一下這段時間

自從小兒子出生以來,每天睡眠不好,加上年底雜事比較多,好久沒有看專業書籍,也沒有寫部落格了。這是病,得改,任何事情都貴在堅持。最近對專案管理和質量管理進行了一些實操性質的研究,實際就是考慮怎麼把規範 標準 體系的要求落實到具體的日常工作中去。下面是一些老生常談 1 流程要梳理清晰。各項工作用的表單,...