歡迎光臨
每天分享高質量文章

Arachne: Core-Aware Thread Management

 

 

 

osdi參考鏈接:https://www.usenix.org/conference/osdi18/presentation/qin

1、 簡介

Arachne是斯坦福大學開發的一個用戶態執行緒調度方案(協程),適用的場景是短生命周期的應用,如memcached和RAMCloud這類,他們的server執行緒生命周期通常只有幾us。使用傳統linux執行緒會帶來較大的開銷和延時。
Arachne的核心思想是讓應用程式根據負載確定自己需要的core數量,程式知道哪些核是分配給自己的,同時控制適量的執行緒在這些core上運行;而core arbiter用來給應用程式分配這些core。
Arachne執行緒庫經過優化,可最大限度的減少cache-misses,同時它可以在220ns內啟動新的用戶執行緒。
由於Arachne是在用戶態實現的,完全不需要修改內核。

2、 用戶態執行緒庫的場景

Arachne被設計用於那些需要處理大量短生命周期請求的服務,同時還要保證較低的延時,比如memcached。
Memchached處理一個請求的時間大概是10us,正常情況下,內核執行緒的開銷太大,無法為所有的請求創建單獨的執行緒;所以memcached使用了執行緒池來處理這個問題,在程式啟動時,創建一批執行緒池,當需要處理新的請求時,由一個分發執行緒將請求分派到某個工作執行緒來處理。
但執行緒池的執行緒數量固定的,在運行時,當可用core比執行緒數少時,將造成多個執行緒共用同一個core,這會導致未被調度的執行緒增加很大的延時,最好的情況是,一個工作執行緒需要單獨使用一個core。
此外,在低負載期間,每個工作執行緒都可能處於idle狀態,這將導致部分core進入節能樣式,而從節能樣式被喚醒的開銷又非常大,這也會增加延時。Arachne嘗試在低負載期間在較少的core上高密集的的運行,這也能獲取更好的表現。
預設情況下,Arachne使用忙轉的調度策略,這將使core的占用率達到100%。

3、 架構

傳統應用在應對低延遲和高吞吐量時,很難權衡。大部分時候,應用程式無法告訴os他們需要多少core,也不知道哪些core是分配給自己使用的。因此應用程式無法調整其內部並形象以匹配可用的core,這可能導致部分core的利用率不足或者負載過高,而需要通過負載均衡來維持的話,將帶來很多額外的開銷,且延遲無法保證。
Arachne作為一個執行緒管理器,通過讓應用程式看到它們正在使用的cores來解決這些問題,core arbiter給程式分配專用core,且分配的core可以保持給該應用使用較長的周期(幾十ms)。應用程式知道自己分配到了哪些core,從而可以在這些cores上合理的分佈它的工作執行緒。
Arachne的特點如下:

  • Arachne可以預估應用程式所需的core數量。
  • Arachne允許每個應用程式定義一個核心策略,該策略在運行時確定應用程式需要多少核心以及如何將執行緒置於可用核心上。
  • 它使用未就緒佇列的調度佇列,該佇列為執行緒創建,調度和同步提供了低延遲和可擴展的機制。
  • 完全在用戶態實現,不需要修改內核;core arbiter使用cpuset實現。Os在運行Arachne程式的同時,也可以運行非arachne的執行緒。

圖1 Arachne架構

Arachne的架構如圖1所示,包括三個組件:

  • core arbiter包含一個獨立的用戶態行程用於管理cores並且將cores分配給不通的應用程式;同時每個應用程式鏈接一個arbiter lib用以和core arbiter做通信。
  • Arachne runtime用於創建一些內核執行緒並且用來管理和執行app的工作執行緒(個人理解這裡的kernel thread是普通的linux執行緒,類似vcpu的概念,通過vcpu管理真正的業務執行緒),並且這裡實現了執行緒的創建/刪除、鎖、條件變數,並且這些實現都是在用戶態,比傳統內核實現要快得多。
  • Arachne runtime還包含一個core的策略器,這個策略器通過arachne runtime收集應用程式的性能指標(負載等),根據指標決定分配多個cores,同時決定哪些執行緒在哪些cores上運行。
  • 用戶可以實現自己的執行緒庫,用以代替archane runtime和core policy。
  • 工作執行緒的調度採用非搶占樣式,如果應用需要長時間運行,需要偶爾呼叫yeild()防止同一個core上其他的執行緒被餓死。

Arachne內部管理兩種cores,一種是托管核,一種是非托管核。其中托管核用來創建arachne執行緒且托管核是由core arbiter來分配的,只有arachne的執行緒才會在托管核上運行;而應用自己創建的,如呼叫std::thread創建的執行緒,將全部步署在非托管核上運行。
在core arbier啟動時,會將所有的物理cores分配到非托管核,並且將所有執行緒放入該非托管核群,這些執行緒創建的新執行緒也在非托管核。後期為了將core分配給Arachne程式,core arbiter會將core從非托管核群刪除,納入到托管核群,並分配給請求者,當應用程式不再需要這些托管核時,core arbiter可以將他們再次收回,並歸還到非托管核群。這種架構可以讓一個程式中同時存在arachne執行緒核普通linux執行緒,當然,托管核群的優先級比非托管核群要高,也就是說有申請core的請求過來,會優先將非托管群的core分配出去,但是非托管群最少也會保留一個core。

4、 降低cache-miss

在傳統調度上,一個core上有一個或者多個runnable queue,當正在運行的執行緒睡眠或者退出時,調度器需要從runnable queue獲取一個新的執行緒來運行,而從runnable queue添加或刪除一個執行緒,需要修改的變數很多,如果queue是多核共享的,即使queue是lockless的,這也會引起大量的cache-miss。所以我們希望執行緒被喚醒時,必須將它添加到它原本所在的core上,但喚醒操作可能是由其它core上的執行緒來呼叫的,這將引起競爭。同時,執行緒的調度狀態也會有讀寫競爭,通常會使用鎖來保護它,但鎖也會導致額外的cache-miss。
為了消除這些cache-miss,arachne不使用runnable queue,而是對當前core相關的所有執行緒進行掃描,直到找到一個可以運行的執行緒為止。
這種設計是基於以下兩個前提:

  • 在給定時間內,一個core只為幾個工作執行緒提供使用。
  • 遍歷執行緒最大的開銷,還是在執行緒state值的cache-miss,這個state通常由不同的core來操作以喚醒執行緒。變數的cache被其他核寫入並掃清直到本核能讀取到新值,大概需要經歷100或更多的cycles,而調度器可以利用這個時間周期,繼續遍歷大量的執行緒。

Arachne對執行緒state變數做了無鎖處理,在執行緒管理結構體中,用一個64位的wakeupTime來標示,當wakeupTime比當前cpu的cycles小,調度器認為該執行緒是可運行的,當調度器切換到該執行緒之前,調度器會將它的wakeupTime置為max。當執行緒進入阻塞時,不需要修改wakeupTime,因為max大於當前cpu cycles計數,任務不會被再次執行。要喚醒執行緒,只需將wakeupTime被設置為0,調度器在下一次調度時,會發現該執行緒成為一個可運行的狀態,因為沒有競爭條件,所以該state不需要使用鎖來保護。

5、 效果

5.1、Memcached:

註:Memcached-A表示使用Arachne版本的mamcached

5.2、RAMCloud:

從性能對比來看,在memcached和ramcloud這種特殊場景下,arachne帶來的性能提升,包括延時和吞吐率都有比較明顯的提升。

赞(0)

分享創造快樂