Hello Lock Contention

在并發(fā)編程里面玩祟,鎖是一個(gè)逃不開的東西腹缩,對(duì)于很多多線程共享的資源,使用鎖是一個(gè)非常的好的辦法空扎,來(lái)支持對(duì)這些資源安全的訪問(wèn)藏鹊。但鎖雖然方便,用不好就容易出現(xiàn)問(wèn)題转锈。

首先大家需要知道盘寡,對(duì)鎖單純的進(jìn)行 Lock 和 Unlock 操作其實(shí)是很快的,但如果涉及到了 Lock Contention撮慨,那么性能就會(huì)有較大的影響了竿痰。

Rust Prometheus Metric Vector

最開始注意到 Lock Contention 問(wèn)題應(yīng)該是在我們使用 Rust Prometheus 庫(kù)的時(shí)候,因?yàn)楫?dāng)時(shí)是按照 Go 的 official library 來(lái)設(shè)計(jì)的砌溺,所以對(duì) Metric Vector 類型的我們也是直接一個(gè)讀寫鎖來(lái)保證里面共享的 HashMap 的安全性影涉。

因?yàn)閹?kù)的細(xì)節(jié)很多同學(xué)不會(huì)關(guān)心,他們會(huì)自然的認(rèn)為 Prometheus 的 API 設(shè)計(jì)的比較高效规伐,所以在一些頻繁需要 metrics 的地方蟹倾,他們直接調(diào)用了 Prometheus 的 API,如果只是單線程還好猖闪,但悲催的是鲜棠,很多邏輯是會(huì)涉及到多線程操作的,然后我們就發(fā)現(xiàn)性能下降非常的嚴(yán)重培慌。譬如這個(gè) PR 就是發(fā)現(xiàn)了這樣的問(wèn)題才臨時(shí)修改的豁陆。

為了解決 Prometheus Metric Vector 的 Lock Contention 問(wèn)題,我們現(xiàn)在臨時(shí)的解決辦法就是使用 Local Metrics吵护,也就是先線程自己去記錄 metrics盒音,然后定期的 flush 到全局的 Prometheus metrics 里面竖配,這樣整體性能就好了很多,但其實(shí)這種做法很丑陋里逆。后面我們準(zhǔn)備參考 RocksDB 的做法进胯,使用 Per CPU 的 metrics 方案,也就是每個(gè) CPU 自己去統(tǒng)計(jì) metrics原押。無(wú)論是不是多線程胁镐,同時(shí)一個(gè)線程的時(shí)間片就只可能在一個(gè) CPU 上面,所以相關(guān)的 metrics 自然可以跟這個(gè) CPU 關(guān)聯(lián)诸衔,詳細(xì)可以參考 Core-local Statistics 這篇文章盯漂。

雖然我之前說(shuō)單純的進(jìn)行 Lock 和 Unlock 很快,但在一些特別高頻的地方笨农,還是會(huì)有影響就缆,所以對(duì)于一些非常快速的操作谒亦,我們后面開始使用 SpinLock竭宰。

Worker Thread Pool

上面說(shuō)了 Prometheus Metrics 遇到的坑,再來(lái)說(shuō)說(shuō)最近遇到的第二個(gè)問(wèn)題份招。大家知道切揭,我們通常會(huì)寫一個(gè) worker thread pool 來(lái)并發(fā)的處理我們的任務(wù),而通常的一個(gè) thread pool 的實(shí)現(xiàn)方式锁摔,就是使用 mutex + condition var 了廓旬,這個(gè)實(shí)現(xiàn)的代碼實(shí)在的太多了,大家直接 Google 吧谐腰,這里就不詳細(xì)說(shuō)明了孕豹。

在 TiKV 里面,我們也使用 mutex + condition var 來(lái)做了一個(gè) thread pool十气,然后這周我在跟另一個(gè)線程調(diào)度問(wèn)題励背,跟蔥頭討論的時(shí)候,突然想 benchmark 下我們的 thread pool 的極限性能桦踊,先寫了一個(gè)簡(jiǎn)單的測(cè)試椅野,測(cè)試很簡(jiǎn)單终畅,就是往一個(gè) channel 里面發(fā)數(shù)據(jù)籍胯,然后還有一個(gè)獨(dú)立的線程不停的收,每一秒統(tǒng)計(jì)收到的數(shù)據(jù)個(gè)數(shù)离福。

然后我開始 benchmark杖狼,悲催的發(fā)現(xiàn),QPS 不到 200 K妖爷,也就是 TiKV 現(xiàn)在極限情況下面單個(gè)節(jié)點(diǎn)最多能處理 200 K 的請(qǐng)求蝶涩,雖然還不錯(cuò)理朋,但我總覺(jué)得可以在高一點(diǎn)。因?yàn)槲覀兪?8 個(gè) worker thread绿聘。如果調(diào)整成 4 個(gè)嗽上,性能會(huì)更好,這個(gè)是正常的熄攘,因?yàn)閷?duì)于這種幾乎沒(méi)啥消耗的 task兽愤,CPU 執(zhí)行起來(lái)非常的快,這時(shí)候反倒是多個(gè)線程的 context switch 會(huì)影響到整體性能挪圾,關(guān)于線程調(diào)度這塊浅萧,以后我詳細(xì)研究了在整理出來(lái),這里先不討論了哲思。

然后我就想洼畅,仍然是 8 個(gè) worker thread,如何還能性能提升棚赔?而且在測(cè)試的使用帝簇,使用 top -H 觀察,會(huì)明顯發(fā)現(xiàn) CPU 打不滿靠益,而我的測(cè)試機(jī)器是 40 Core己儒,不存在性能問(wèn)題。我只能將我的目光鎖定到了 mutex 的 Lock Contention 上面捆毫,因?yàn)樵谖覀兊膶?shí)現(xiàn)中闪湾,給 task queue 添加任務(wù)以及從 worker thread 獲取任務(wù),都是在 mutex 里面進(jìn)行的绩卤,也就是每次操作都會(huì)極大的概率遇到 Lock Contention途样。

于是我先做了第一個(gè)優(yōu)化,將 task queue 從 mutex 里面移出來(lái)濒憋,使用 SpinLock何暇,測(cè)試發(fā)現(xiàn) QPS 上升到 230 K,然后用 perf record 兩次測(cè)試凛驮,diff 對(duì)比:

# Event 'cycles:ppp'
#
# Baseline    Delta  Shared Object        Symbol
# ........  .......  ...................  ...................................................................................
#
     8.40%   +7.58%  [kernel.vmlinux]     [k] _raw_spin_lock
     3.29%   -0.17%  [kernel.vmlinux]     [k] __schedule
     1.02%   +0.09%  [kernel.vmlinux]     [k] futex_wait_setup
     0.96%   -0.06%  [kernel.vmlinux]     [k] finish_task_switch

發(fā)現(xiàn)之前的測(cè)試比 SpinLock 的多了 7.5% 的 _raw_spin_lock 開銷裆站,這個(gè)當(dāng)然不是我們自己實(shí)現(xiàn)的 SpinLock,而是內(nèi)核 Mutex 里面的黔夭。

但即使這樣宏胯,我仍然發(fā)現(xiàn),worker thread 的 CPU 跑不滿本姥,懷疑是 worker 線程取任務(wù)太快肩袍,而 Producer 線程沒(méi)法那么快的把 task 加到隊(duì)列里面,于是 worker 線程因?yàn)闆](méi)有獲取到任務(wù)婚惫,只能重新進(jìn)入 condition wait 狀態(tài)氛赐。于是我暴力的改了一下獲取 task 的代碼魂爪,循環(huán) 10 次,如果沒(méi)取到任務(wù)艰管,就調(diào)用 thread 的 yield 函數(shù)讓出時(shí)間片滓侍,下一輪繼續(xù)嘗試。然后這次 QPS 漲了不少牲芋,直接到了 300 K+ 了粗井,當(dāng)然 CPU 也高了不少了。

但這個(gè)測(cè)試只是能評(píng)估我們極限性能街图,好指導(dǎo)我們后續(xù)的優(yōu)化浇衬。譬如現(xiàn)在我就知道我們最多不到 200 K 的 QPS,如果還是這個(gè)模型餐济,其它地方再怎么優(yōu)化耘擂,也還是這么多。

說(shuō)到這里絮姆,在聊一件性能測(cè)試的時(shí)候遇到的奇怪的事情醉冤,我在使用 perf stat 分析性能的時(shí)候,發(fā)現(xiàn)使用 SpinLock 的版本 QPS 能到 400 K篙悯,但之前的版本仍然不到 200 K蚁阳。這個(gè)結(jié)果實(shí)話當(dāng)時(shí)第一眼看到,是非常的奇怪的鸽照,后來(lái)思考了一下螺捐,因?yàn)?perf stat 其實(shí)是會(huì)在一些內(nèi)核的地方進(jìn)行 probe 的,這個(gè)會(huì)影響性能矮燎,可能因?yàn)檫@樣定血,導(dǎo)致 worker thread 那邊的 condition wait 以及調(diào)度變慢了,結(jié)果 Producer 那邊扔進(jìn)去更多的任務(wù)诞外,使得 worker 后面一直能取到任務(wù)澜沟,不會(huì)進(jìn)入 mutex 和 condition wait 了。而對(duì)于我們之前的版本來(lái)說(shuō)峡谊,無(wú)論怎樣茫虽,都要通過(guò) mutex 來(lái)進(jìn)行 task 操作,自然就不會(huì)出現(xiàn) SpinLock 這樣的情況既们。對(duì)于這個(gè)問(wèn)題濒析,這只是我的猜測(cè),我也跟其他對(duì) Linux 系統(tǒng)很精通的同學(xué)討論過(guò)這個(gè)問(wèn)題贤壁,大家都覺(jué)得這個(gè)解釋比較合理悼枢,只是后面我也懶得去追下去了,等后面有空了脾拆,重新折騰調(diào)度的時(shí)候再看看吧馒索。

Perf script

在測(cè)試的時(shí)候,我使用 perf trace 或者 perf record 大概知道了不同方案 mutex 的調(diào)用情況名船,但其實(shí)我最想知道的是到底出現(xiàn)了多少次 Lock Contention绰上,因?yàn)槲覒岩删褪且驗(yàn)檫@個(gè)導(dǎo)致的性能差異。

雖然能通過(guò) systemtap 來(lái)搞定這個(gè)問(wèn)題渠驼,但我不知道為啥頭腦抽風(fēng)了蜈块,想用 perf 來(lái)搞定。開始想的是直接 perf stat 的時(shí)候傳入相應(yīng)的 event 來(lái)做迷扇,但我在 perf list 里面沒(méi)找到對(duì)應(yīng)的 event百揭,Google 了下也沒(méi)啥好的結(jié)果,這個(gè)其實(shí)還是我對(duì)底層了解的不夠蜓席,真熟練了直接源碼一翻就知道了器一。但幸運(yùn)的是,我突然發(fā)現(xiàn)了 perf script厨内,之前這個(gè)玩意我只是在生成火焰圖的時(shí)候見(jiàn)過(guò)祈秕,但自己并沒(méi)有用過(guò)。昨天才知道雏胃,它其實(shí)是一個(gè)非常強(qiáng)大的東西请毛,也就是說(shuō),我們可以寫自己的 script 來(lái)分析 perf 采樣的數(shù)據(jù)瞭亮,而使用的 script language 并不需要像 systemtap 那樣重新學(xué)一門新的方仿,就是 perl 或者 python,當(dāng)然 perl 就算了统翩,python 雖然我不喜歡兼丰,但至少之前還寫過(guò) 2 年,完全夠用了唆缴。具體的 perf script 的東西鳍征,后面有時(shí)間可以在詳細(xì)的說(shuō)明。

我使用 perf script -l 看現(xiàn)在有啥可用的腳本面徽,幸運(yùn)的發(fā)現(xiàn)了一個(gè) futex-contention艳丛,直接使用 perf script report futex-contention 得到了如下的輸出:

test[132777] lock 7faaf8a220c0 contended 199 times, 132761 avg ns
test[132782] lock 7faaf8a22090 contended 1171 times, 519948 avg ns
test[132778] lock 7faaf8a22090 contended 1145 times, 2004888 avg ns
test[132778] lock 7faaf8a220c0 contended 226 times, 1694905 avg ns
test[132779] lock 7faaf8a22090 contended 1199 times, 2745749 avg ns

然后再把這個(gè)結(jié)果貼到 Excel 里面,對(duì) times 匯總趟紊,確定了 SpinLock 的版本比原來(lái)的版本少了將近 15% 的 Lock Contention氮双。

總結(jié)

其實(shí)上面折騰了這么多,看起來(lái)沒(méi)折騰出啥東西霎匈,無(wú)非就是知道了 Lock Contention 對(duì)性能會(huì)有影響戴差,但對(duì)我來(lái)說(shuō),更大的收獲在于在排查問(wèn)題的時(shí)候铛嘱,對(duì)系統(tǒng)理解更加深入暖释,對(duì) perf 這些工具用起來(lái)更加熟練了袭厂。

TiKV 開發(fā)到現(xiàn)在,很多調(diào)優(yōu)工作其實(shí)已經(jīng)涉及到了跟系統(tǒng)和硬件的結(jié)合上面球匕,之前我們還能假設(shè)更底層對(duì)我們是一個(gè)黑盒纹磺,但現(xiàn)在為了更好的性能,需要我們對(duì)更下面抽絲剝繭亮曹,開始深入理解和掌握了橄杨。如果你對(duì) Linux 非常感興趣,想折騰下 DPDK 等跟硬件更緊密的優(yōu)化開發(fā)照卦,歡迎給我發(fā)郵件式矫,我的郵箱 tl@pingcap.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末役耕,一起剝皮案震驚了整個(gè)濱河市采转,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹄葱,老刑警劉巖氏义,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異图云,居然都是意外死亡惯悠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绿映,你說(shuō)我怎么就攤上這事∏橛” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵摹恨,是天一觀的道長(zhǎng)筋岛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晒哄,這世上最難降的妖魔是什么睁宰? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮寝凌,結(jié)果婚禮上柒傻,老公的妹妹穿的比我還像新娘。我一直安慰自己较木,他們只是感情好红符,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般预侯。 火紅的嫁衣襯著肌膚如雪致开。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天雌桑,我揣著相機(jī)與錄音喇喉,去河邊找鬼祖今。 笑死校坑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的千诬。 我是一名探鬼主播耍目,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼徐绑!你這毒婦竟也來(lái)了邪驮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤傲茄,失蹤者是張志新(化名)和其女友劉穎毅访,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盘榨,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喻粹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了草巡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片守呜。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖山憨,靈堂內(nèi)的尸體忽然破棺而出查乒,到底是詐尸還是另有隱情,我是刑警寧澤郁竟,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布玛迄,位于F島的核電站,受9級(jí)特大地震影響棚亩,放射性物質(zhì)發(fā)生泄漏蓖议。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一蔑舞、第九天 我趴在偏房一處隱蔽的房頂上張望拒担。 院中可真熱鬧,春花似錦攻询、人聲如沸从撼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)低零。三九已至婆翔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掏婶,已是汗流浹背啃奴。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雄妥,地道東北人最蕾。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像老厌,于是被迫代替她去往敵國(guó)和親瘟则。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容