成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從CPU冒煙到絲滑體驗:算法SRE性能優(yōu)化實戰(zhàn)全揭秘

開發(fā) 架構(gòu)
本文分享的場景和實操經(jīng)驗,旨在拋磚引玉,幫助各位同學(xué)掌握深度性能分析的方法論,避免走彎路,更高效地解決工程難題。希望每位研發(fā)和SRE同學(xué),都能從微妙的細節(jié)中捕捉優(yōu)化機會,讓應(yīng)用在極致性能的路上穩(wěn)步前進。?

一、引言

在算法工程中,大家一般關(guān)注四大核心維度:穩(wěn)定、成本、效果、性能。

其中,性能尤為關(guān)鍵——它既能提升系統(tǒng)穩(wěn)定性,又能降低成本、優(yōu)化效果。因此,工程團隊將微秒級的性能優(yōu)化作為核心攻堅方向。

本文將結(jié)合具體案例,分享算法SRE在日常性能優(yōu)化中的寶貴經(jīng)驗,助力更多同學(xué)在實踐中優(yōu)化系統(tǒng)性能、實現(xiàn)業(yè)務(wù)價值最大化。

二、給浮點轉(zhuǎn)換降溫

算法工程的核心是排序,而排序離不開特征。特征大多是浮點數(shù),必然伴隨頻繁的數(shù)值轉(zhuǎn)換。零星轉(zhuǎn)換對CPU無足輕重,可一旦規(guī)模如洪水傾瀉,便會出現(xiàn)CPU瞬間飆紅、性能斷崖式下跌的情況,導(dǎo)致被迫堆硬件,白白抬高成本開銷。

例如:《交易商詳頁相關(guān)推薦 - neuron-csprd-r-tr-rel-cvr-v20-s6》 特征處理占用CPU算力時間的61%。其中大量工作都在做Double浮點轉(zhuǎn)換,如圖所示:

圖片圖片

優(yōu)化前CPU時間占比 18%

Double.parseDouble、Double.toString是JDK原生原子API了,還能優(yōu)化?直接給答案:能!

浮點轉(zhuǎn)字符串:Ryu算法

https://github.com/ulfjack/ryu

Ryu算法,用“查表+定長整數(shù)運算”徹底摒棄“動態(tài)多精度運算+內(nèi)存管理”的重開銷,既正確又高效。

算法的完整正確性證明:https://dl.acm.org/citation.cfm? doid=3296979.3192369

 偽代碼說明 

// ——“普通”浮點到字符串(高成本)——
void convertStandard(double d, char *out) {
    // 1. 拆分浮點:符號、指數(shù)、尾數(shù)
    bool sign = (d < 0);
    int  exp  = extractExponent(d);    // 提取二進制指數(shù)
    uint64_t mant = extractMantissa(d);
    
    // 2. 構(gòu)造大整數(shù):mant × 2^exp —— 可能要擴容內(nèi)存
    BigInt num = BigInt_from_uint64(mant);
    num = BigInt_mul_pow2(num, exp);    // 多精度移位,高開銷
   
    // 3. 逐位除以 10 生成十進制,每次都是多精度除法
    //    ——每次 divMod 都要循環(huán)內(nèi)部分配和多精度運算
    char buf[32];
    int  len = 0;
    while (!BigInt_is_zero(num)) {
        BigInt digit, rem;
        BigInt_divmod(num, 10, &digit, &rem);  // 慢:多精度除法
        buf[len++] = '0' + BigInt_to_uint32(digit);
        BigInt_free(num);
        num = rem;
    }
    
    // 4. 去除多余零、插入小數(shù)點和符號
    formatOutput(sign, buf, len, out);
}




// ——Ryu 方法(低成本)——
void convertRyu(double d, char *out) {
    // 1. 拆分浮點:符號、真實指數(shù)、尾數(shù)(隱含1)
    bool sign = (d < 0);
    int  e2   = extractBiasedExponent(d) - BIAS;
    uint64_t m2 = extractMantissa(d) | IMPLIED_ONE;
    
    // 2. 一次查表:獲得 5^k 和對應(yīng)位移量
    //    ——預(yù)先計算好,運行時無動態(tài)開銷
    int      k     = computeDecimalExponent(e2);
    uint64_t pow5  = POW5_TABLE[k];        // 只讀數(shù)組(cache 友好)
    int      shift = SHIFT_TABLE[k];
    
    // 3. 單次 64×64 位乘法 + 右移 —— 固定時間
    __uint128_t prod = ( __uint128_t )m2 * pow5;
    uint64_t    v    = (uint64_t)(prod >> shift);
    
    // 4. 固定最多 ~20 次小循環(huán),v%10 生成每位數(shù)字
    //    ——循環(huán)次數(shù)上限,與具體數(shù)值無關(guān)
    char buf[24];
    int  len = 0;
    do {
        buf[len++] = '0' + (v % 10);
        v /= 10;
    } while (v);
   
    // 5. 去零、插小數(shù)點、加符號:輕量字符串操作
    formatShort(sign, buf, len, k, out);
}

傳統(tǒng)方法 vs. Ryu算法對比:

算法比較

“普通”算法


Ryu算法


內(nèi)存分配

BigInt動態(tài)擴容 + 釋放 →heap分配/回收成本高



全/靜態(tài)表 + 棧數(shù)組,無malloc→ 零動態(tài)分配

算術(shù)成本

頻繁多精度除法

(數(shù)百納秒)

單次64位乘法+位移

(約30-40納秒)

循環(huán)次數(shù)

取決于浮點數(shù)數(shù)值

難以預(yù)測

固定次數(shù)

易于優(yōu)化和預(yù)測

緩存友好

內(nèi)存分散

不利CPU緩存

棧上集中

CPU緩存友好

字符串轉(zhuǎn)浮點:Fast_Float算法

https://github.com/wrandelshofer/FastDoubleParser

相比Java自帶的Double.parseDouble使用復(fù)雜狀態(tài)機(如BigDecimal或 BigInteger)來處理各種情況,F(xiàn)astDoubleParser使用以下優(yōu)化策略。

FastDoubleParser 優(yōu)化策略

※  分離階段
  • 將輸入拆分為三個部分:significand、exponent、special cases(如 NaN, Infinity)。
  • 解析時直接處理整數(shù)位和小數(shù)位的組合。
※  整型加速 + 倍數(shù)轉(zhuǎn)換
  • 在范圍允許的情況下使用“64位整數(shù)直接表示”有效位。
  • 再通過預(yù)計算的“冪次表(10? 或 2?)”進行快速縮放,避免慢速浮點乘法。
※  避免慢路徑
  • 避免使用BigDecimal或字符串轉(zhuǎn)高精度,再轉(zhuǎn)回double的慢路徑。
  • 對于大多數(shù)輸入,整個解析過程不涉及任何內(nèi)存分配。
※  SIMD加速(原版 C++)

在C++中使用SIMD指令批量處理字符,Java版受限于JVM,但仍通過循環(huán)展開等技術(shù)盡量進行優(yōu)化。

 轉(zhuǎn)換思路 

Input: "123.45e2"
1. 拆分成:
   significand = 12345 (去掉小數(shù)點)
   exponent = 2 - 2 = 0  // 小數(shù)點后兩位,但有 e2
2. 快速轉(zhuǎn)換:
   result = 12345 * 10^0 = 12345.0
3. 最終使用 Double.longBitsToDouble 構(gòu)造結(jié)果

壓測報告

Double 字符解析相對JDK原生API 4.43倍 加速Double 字符解析相對JDK原生API 4.43倍 加速

代碼優(yōu)化樣例

通過多層判斷,盡可能不讓Object o做toString()操作。

減少toString觸發(fā)的可能減少toString觸發(fā)的可能

工具類 替換浮點轉(zhuǎn)換算法工具類 替換浮點轉(zhuǎn)換算法

工具類 替換浮點轉(zhuǎn)換算法

性能實測效果

啟用Ryu、Fast_Float算法替換JDK原生浮點轉(zhuǎn)換,效果如下:

優(yōu)化后CPU時間占比 0.19%【性能提升(18-0.19)/18=98%】優(yōu)化后CPU時間占比 0.19%【性能提升(18-0.19)/18=98%】

CPU實際獲得50%收益CPU實際獲得50%收益

RT實際獲得25%左右性能收益RT實際獲得25%左右性能收益


小結(jié)

告別原生JDK浮點轉(zhuǎn)換的高昂代價,擁抱Ryu與FastDoubleParser,讓CPU從繁忙到清閑,性能“回血”,節(jié)約的成本大家可以吃火鍋。

三、拔掉詭異的GC毛刺

小堆GC問題

特征維度多時內(nèi)存壓力大,GC問題可以預(yù)期。但很多同學(xué)可能沒有見過,小堆場景,GC也可能頻繁觸發(fā),甚至引發(fā)異常。

如圖所示:18GB堆 擴容 -> 30GB堆,均出現(xiàn)RT99周期脈沖,致使5~6%的失敗率。

社區(qū)瀑布流廣告投放-Neuron精排   因GC導(dǎo)致錯誤社區(qū)瀑布流廣告投放-Neuron精排 因GC導(dǎo)致錯誤

GC問題分析

首先這是GC問題,其次增加了近1倍的內(nèi)存,沒有絲毫緩解,判斷這應(yīng)該是個偽GC問題。

Neuron主要功能就是拿著特征轉(zhuǎn)向量做排序。一般特征量都是億起步,多的達十億,因此特征緩存必不可少。但是這個場景,僅僅是將1700個左右的廣告特征信息進行了緩存,為什么對象內(nèi)存會出現(xiàn)周期性的脈沖?

年輕代+老年代 周期共振脈沖年輕代+老年代 周期共振脈沖

如圖所示,關(guān)鍵的問題在于“共振”。因此要用放大鏡看問題,再如圖所示:

共振點 放大共振點 放大

共振點CPU峰值水位:28%共振點CPU峰值水位:28%

GC 暫停時間GC 暫停時間


線索

矛盾點


疑惑點

老年代回收 3GB

老年代3GB回收,對于C4垃圾回收器,應(yīng)該毫無壓力


年輕代徒增 9GB


老年代GC,為什么年輕代會同步往上飚?

年輕代瞬間回收 9GB


年輕代內(nèi)存飚升后,為什么瞬間又把內(nèi)存釋放?

共振點CPU無壓力

兩代整體回收12GB,對于C4垃圾回收器,應(yīng)該毫無壓力



GC窗口期間,CPU算力充足,為什么會導(dǎo)致 RT99 成倍往上飚?

到這里,其實問題已經(jīng)很明顯了:

  • C4作為世界頂級垃圾回收器,GC的能力不用懷疑,STW(Stop-The-World)的時間理論是亞毫秒級。
  • 如果GC能力沒問題,算力又充足,那么造成RT99翻倍的原因:要么是線程在等數(shù)據(jù),要么是線程忙不過來。
  • Neuron堆內(nèi)存大頭是緩存,那么老年代回收的數(shù)據(jù)一定是緩存數(shù)據(jù),年輕代一定是在回補緩存缺口。

為什么會有這個邏輯?因為緩存命中率一直是 99.9%【1700個廣告條目】,如圖所示:

圖片圖片

在極高緩存命中率的場景下,僅清理少量緩存條目,也可能造成“緩存缺口”。緩存缺口本質(zhì)上也是一次“中斷”,線程被迫等待或執(zhí)行數(shù)據(jù)回補,導(dǎo)致性能抖動。

為方便理解,類比“缺頁中斷”(Page Fault):當(dāng)程序訪問未加載的內(nèi)存頁時,操作系統(tǒng)必須中斷執(zhí)行、加載數(shù)據(jù),再繼續(xù)運行。

解決方案

首先是緩存命中率一定是越高越好,99.9%的命中率沒毛病。問題出在1700條廣告緩存條目,究竟為何必須如此頻繁地設(shè)置過期?【TTL: 60~90s】

原因是:業(yè)務(wù)期望廣告特征,能夠盡可能實時更新。

緩存失效策略緩存失效策略

失效時間 60~90s失效時間 60~90s

關(guān)鍵在于,緩存條目必須及時失效,卻又不能因GC過度而引發(fā)性能問題。從觀察結(jié)果來看,年輕代的GC沒有對RT99的性能產(chǎn)生明顯影響,這說明年輕代GC的力度恰到好處,不會造成頻繁的“緩存缺口”。既然如此,我們考慮:如果能徹底規(guī)避老年代GC,性能瓶頸的問題是否就能迎刃而解?

因此,我們嘗試大幅提高對象晉升到老年代的門檻,直接提升了幾個數(shù)量級。

增加JVM參數(shù):


-XX:GPGCTimeStampPromotionThresholdMS # 對象晉升老年代前的時間閾值
默認值:2000  調(diào)整為:6000000 (1.6小時)


-XX:GPGCOldGCIntervalSecs # 老年代固定GC時間推薦。注意:并不是關(guān)閉 OldGC
默認值:600 調(diào)整為:600000

在這個場景中,實際有效的對象并不多,最多不過5GB。 其余大部分都是生命周期不超過2分鐘的短期廣告特征條目(約1700條)。這種短生命周期、低占用的場景完全靠年輕代GC就能輕松支撐,根本不需要啟用分代GC。

實際測試一天后,完全印證了這一判斷:GC抖動、RT99抖動以及錯誤率抖動全都徹底消失,同時內(nèi)存也沒有出現(xiàn)任何泄漏。

GC 毛刺消失GC 毛刺消失

RT99失敗率 毛刺峰值降至 1/10 +RT99失敗率 毛刺峰值降至 1/10 +

小結(jié)

C4的分代GC對大堆確實有奇效,但放在小堆場景里,非要套個復(fù)雜架構(gòu),就成了典型的“形式主義”

大堆適用,小堆不行。

四、是誰偷走了RT時間

業(yè)務(wù)瓶頸的卡點

最近算法特征多了,推理成本就高了;RT一長,用戶體驗就垮了;產(chǎn)品一急,秒開優(yōu)化就立項了。

全業(yè)務(wù)鏈路都已鎖定 RT 優(yōu)化目標(biāo),社區(qū)個性化精排也在其中,可這一鏈路優(yōu)化阻力最大——RT99長期卡在120ms 以上,始終難以突破。

圖片圖片

活用三昧真火

性能分析必看CPU火焰圖。一看圖就是GC問題。

GC日志分析,年輕代+老年代,堆積起來約150GB,而堆內(nèi)存才給108GB,怎么做到的?->>> 頻繁GC!

GC算力消耗占比 超50%GC算力消耗占比 超50%

至少要 150GB 勉強夠用至少要 150GB 勉強夠用

高頻GC

看看哪里分配內(nèi)存比較瘋狂,如圖內(nèi)存分配火焰圖所示:

圖片圖片

內(nèi)存分配壓力指向兩大熱點

※  Dump

業(yè)務(wù)剛需,大量序列化點對象帶來的瞬時垃圾情有可原。

※  特征

真正的“吞金獸”——獨占超過50%的堆。業(yè)務(wù)方解釋:當(dāng)前500萬特征才勉強把命中率抬到80%,想繼續(xù)往上,只能指數(shù)級內(nèi)存擴容,總特征數(shù)10億+。堆已拉到128GB,找不到更大規(guī)格的機器。

也就是說內(nèi)存主要被特征吞掉了,優(yōu)化空間基本沒有。

如果優(yōu)化止步于此,顯然無法滿足業(yè)務(wù)方的期望,于是我們進一步深入到Wall火焰圖進行更精細的分析。

圖片圖片

Wall火焰圖同時捕獲了CPU執(zhí)行與IO等待,因此不能簡單地以棧頂寬度判斷性能瓶頸。否則只會發(fā)現(xiàn)線程池空閑的等待任務(wù),看似正常,但真正的性能瓶頸卻隱藏在細節(jié)中。

因此,我們需要放大視角,聚焦到具體的業(yè)務(wù)邏輯堆棧位置。在這個案例中,一旦放大便能發(fā)現(xiàn)顯著問題:特征讀取階段的IO等待時間,竟然超過了遠程DML推理與Kafka Dump的總耗時。這直接說明,所謂的80%特征緩存命中率存在明顯的緩存擊穿現(xiàn)象,大量請求可能被迫穿透至遠端Redis或C引擎進行加載,其耗時成本遠高于本地緩存命中的場景。

逐幀跟蹤確認逐幀跟蹤確認

通過進一步的Trace跟蹤分析,我們的猜測得到了驗證。

圖片圖片

通過和C引擎團隊聯(lián)合排查發(fā)現(xiàn),現(xiàn)有架構(gòu)采用了早期的部署模式,其中為索引分片路由而設(shè)立的中間Proxy層成為性能瓶頸,其RT999甚至超過100ms。這種架構(gòu)帶來的問題在于,上游業(yè)務(wù)對特征數(shù)量需求極大,即使緩存已擴大到500萬條目,也僅能達到80%的命中率。算法工程團隊通過對特征請求進行多層拆分及異步并發(fā)查詢優(yōu)化,但仍有少量長尾特征無法命中緩存,只能依靠C引擎響應(yīng)。一旦任何一批次特征查詢觸發(fā)了C引擎的慢查詢,這一請求的整體RT勢必大幅提升,甚至可能超時。

好在C引擎同時提供了一種更先進的垂直多副本部署模式,能夠去除Proxy這一中心化的瓶頸組件。未來的新架構(gòu)仍會保留索引分片設(shè)計,但會利用旁路方式實現(xiàn)完全的去中心化。

圖片圖片

小結(jié)

通過Wall火焰圖深入分析RT性能瓶頸,并結(jié)合Trace工具驗證猜想,是優(yōu)化系統(tǒng)性能不可或缺的關(guān)鍵步驟。

五、結(jié)語:性能優(yōu)化無止盡

性能優(yōu)化沒有終點,只有下一個起點。每次性能的提升,不僅是對技術(shù)邊界的突破,更是為業(yè)務(wù)創(chuàng)造了更多可能性。本文分享的場景和實操經(jīng)驗,旨在拋磚引玉,幫助各位同學(xué)掌握深度性能分析的方法論,避免走彎路,更高效地解決工程難題。希望每位研發(fā)和SRE同學(xué),都能從微妙的細節(jié)中捕捉優(yōu)化機會,讓應(yīng)用在極致性能的路上穩(wěn)步前進。

責(zé)任編輯:武曉燕 來源: 得物技術(shù)
相關(guān)推薦

2021-11-17 08:16:03

內(nèi)存控制Go

2024-09-12 14:51:27

2022-08-16 08:37:09

視頻插幀深度學(xué)習(xí)

2018-03-30 18:17:10

MySQLLinux

2022-12-20 09:09:27

ViteWebpack

2021-01-18 18:42:33

工具調(diào)優(yōu)開發(fā)

2025-06-03 02:55:00

2025-02-20 09:27:46

2009-07-08 15:11:58

JVM GC調(diào)整優(yōu)化

2009-04-20 08:51:50

MySQL查詢優(yōu)化數(shù)據(jù)庫

2019-12-10 08:10:35

LinuxCPU性能優(yōu)化

2024-05-30 11:44:37

2017-03-29 14:44:20

網(wǎng)絡(luò)性能優(yōu)化

2022-05-17 09:02:30

前端性能優(yōu)化

2022-08-14 14:32:06

接口優(yōu)化

2025-04-18 08:24:22

2009-07-07 22:33:49

2023-03-29 20:06:27

IdeaHub

2020-05-09 11:26:43

ChromeFirefoxWindows
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: a级毛片基地 | 色视频在线播放 | 精品视频久久久 | 国产精品2区| 欧美日韩国产不卡 | 久久精品无码一区二区三区 | 中文字幕一区二区三区四区五区 | 久久精品小视频 | 嫩草一区二区三区 | 男女爱爱福利视频 | 天天拍天天色 | 一区二区国产精品 | 亚洲喷水| 欧美日韩1区2区 | 久久成人精品 | 亚洲精品一区二区三区中文字幕 | 国产日韩欧美一区 | 一区二区三区四区在线 | 黄色av网站在线免费观看 | 久久精品青青大伊人av | 日韩一区二区在线看 | 在线播放亚洲 | 色婷婷国产精品 | 欧美精品福利视频 | 亚洲欧美日韩国产综合 | 日韩中文在线观看 | 精品国产乱码久久久 | 又黄又色 | 黄色片在线 | 成人精品鲁一区一区二区 | 狠狠干夜夜草 | 欧美精品综合在线 | 天天拍夜夜爽 | 视频二区在线观看 | 青青久久久 | 欧美日韩精品在线一区 | 五月天天丁香婷婷在线中 | 精品乱人伦一区二区三区 | 欧美福利一区 | 成人免费激情视频 | 国产一区二区成人 |