試:編譯、NUMA綁核與可復(fù)現(xiàn)避坑)
簡介LLCbench 是一套面向 Linux 平臺(tái)的低層表征基準(zhǔn)測(cè)試工具集整合了 MPBench、CacheBench 與 BLASBench 三種測(cè)試方法此包取其中 CacheBench 部分用于衡量處理器各級(jí)緩存L1/L2/LLC的讀寫帶寬與訪問延遲適合體系結(jié)構(gòu)研究、服務(wù)器選型對(duì)比或性能調(diào)優(yōu)的開發(fā)者與高校師生使用。壓縮包共 81 個(gè)文件、約 83KB含 cachebench.c 等 C 源碼、Makefile 構(gòu)建腳本、llcbench.h 頭文件與 user.def 參數(shù)定義以及 sys.linux-mpich、sys.solaris、sys.ppc 等十余份平臺(tái)配置模板配合 doc 目錄下的 html/tex 說明文檔和 ps、gp 繪圖腳本可完成從編譯、跑分到結(jié)果出圖的完整流程。已有 873 人學(xué)習(xí)體量輕便解壓后按對(duì)應(yīng)平臺(tái)模板即可編譯驗(yàn)證緩存帶寬與延遲表現(xiàn)。1. cachebench 這類跑緩存性能的工具很多人第一次接觸是從一份 llcbench.tar.gz 開始的包里幾個(gè)用 C 寫的 Linux 開源工具專做緩存層次和處理器性能測(cè)試。上周有人拿一臺(tái)二手工作站來問為什么同一份包在他的機(jī)器上編譯完cachebench 跑出來的 L3 帶寬比標(biāo)稱值低將近一個(gè)數(shù)量級(jí)。我上去看scaling_governor還是powersave測(cè)試進(jìn)程在 NUMA 節(jié)點(diǎn)間來回遷移工作集上限只設(shè)到 8 MB連 L3 都沒越過去。工具本身沒問題問題出在裝完就開跑。緩存測(cè)試對(duì)環(huán)境的敏感度比一般壓測(cè)高得多頻率、綁核、大頁、編譯器優(yōu)化等級(jí)任何一項(xiàng)沒配好出來的數(shù)字看著像結(jié)果實(shí)際是不可復(fù)現(xiàn)的噪聲。這篇裝的東西不復(fù)雜一份源碼包、一個(gè) GCC、幾個(gè)內(nèi)核開關(guān)但順序和參數(shù)得對(duì)。2. 裝前先量體llcbench 的依賴清單與 GCC 工具鏈選型2.1 先用 lscpu 和 getconf 摸清這臺(tái)機(jī)器的緩存層級(jí)cachebench 的橫軸是工作集大小縱軸是訪存帶寬曲線上的臺(tái)階就是緩存邊界。你要是不知道這臺(tái)機(jī)器的 L1d、L2、LLC 各多大、LLC 是每個(gè)核心獨(dú)享還是整顆 CPU 共享那看到拐點(diǎn)也不知道對(duì)不對(duì)看到?jīng)]拐點(diǎn)也不知道是程序問題還是參數(shù)問題。所以裝之前先花兩分鐘把硬件底賬抄下來存檔留作后面判讀的依據(jù)。# 一次看清拓?fù)?、緩存?NUMA 節(jié)點(diǎn)數(shù) lscpu | grep -Ei model name|socket|core|thread|numa|cache # getconf 讀的是內(nèi)核導(dǎo)出的值和 lscpu 交叉驗(yàn)證 getconf -a | grep -iE CACHE_SIZE|CACHE_LINE|CACHE_ASSOC # sysfs 里的 index 編號(hào)才是權(quán)威index0L1d, index2L2, index3LLC for i in /sys/devices/system/cpu/cpu0/cache/index*; do echo $i cat $i/level $i/type $i/size $i/shared_cpu_list done第一條命令里的socket和numa決定后面怎么綁核第二條的LEVEL1_DCACHE_SIZE、LEVEL3_CACHE_SIZE單位是字節(jié)用來給 cachebench 的-m參數(shù)定上限提供參考。最關(guān)鍵是第三條shared_cpu_list會(huì)告訴你 LLC 被哪些邏輯核共享比如輸出0-15說明這 16 個(gè)邏輯核共享同一個(gè) LLC那測(cè)試時(shí)把進(jìn)程綁到這 16 個(gè)核之外的任何一個(gè)訪存就要走跨片路徑帶寬數(shù)字會(huì)明顯掉一截。這一步做完本機(jī)的緩存地圖就有了順手記進(jìn)一個(gè)hardware.txt以后換機(jī)器、換內(nèi)核版本都有對(duì)照。2.2 編譯依賴GCC、make、MPI、BLAS 哪些是必須的llcbench 這個(gè)包里通常不止一個(gè)可執(zhí)行程序cachebench 是純 C 加標(biāo)準(zhǔn)庫只需要 GCC 和 make要是你還想跑 mpbench就得有 MPI 的編譯包裝器mpiccblasbench 則要鏈接一個(gè) BLAS 實(shí)現(xiàn)。很多人一上來把整條 MPI 棧、整套 BLAS 全拉下來裝完發(fā)現(xiàn)只是測(cè)個(gè)緩存白白多出幾十個(gè)包和一堆環(huán)境變量沖突。按需裝才是對(duì)的。組件版本要求Debian/Ubuntu 包名RHEL/Rocky 包名GCC7 以上913 都穩(wěn)build-essentialgcc、glibc-develmake4.xmakemakenumactl2.xnumactlnumactlcpupower需跟內(nèi)核版本對(duì)齊linux-tools-generickernel-toolsperf同上linux-tools-commonperfMPI可選OpenMPI 4.xlibopenmpi-dev、openmpi-binopenmpi-develOpenBLAS可選0.3.xlibopenblas-devopenblas-devel# 最小可用集只跑 cachebench 的話這一行就夠 sudo apt-get update sudo apt-get install -y build-essential make numactl linux-tools-common linux-tools-generic # 確實(shí)要跑 mpbench / blasbench 時(shí)再補(bǔ)這兩行 sudo apt-get install -y libopenmpi-dev openmpi-bin sudo apt-get install -y libopenblas-devRHEL 系把上面的包名換成表里右列即可注意dnf install openmpi-devel裝完mpicc落在/usr/lib64/openmpi/bin下默認(rèn)不在 PATH 里得用module load mpi/openmpi-x86_64或者手動(dòng)把那個(gè)目錄加進(jìn)去否則which mpicc一無所獲。cpupower和perf一定要跟當(dāng)前內(nèi)核同一個(gè)版本號(hào)版本錯(cuò)位時(shí) cpupower 會(huì)報(bào)Invalid argument之類的錯(cuò)perf 則可能直接提示找不到符號(hào)這類問題九成是版本沒對(duì)齊而不是權(quán)限。2.3 解包與目錄規(guī)劃源碼別扔在 /root 下源碼目錄和結(jié)果目錄要分開而且最好不要放在家目錄。原因很實(shí)際測(cè)試前要清 page cache、要關(guān) swap跑完還會(huì)生成一堆 CSV 和日志混在/root里三個(gè)月后你自己都分不清哪份數(shù)據(jù)是哪臺(tái)機(jī)器的。我一般固定用/usr/local/src放源碼/opt/bench放二進(jìn)制和結(jié)果。# 先看包里的頂層結(jié)構(gòu)確認(rèn)解出來是單目錄還是散開的 tar -tzf llcbench.tar.gz | head -30 # 解到固定位置源碼保持只讀心態(tài)不在這里改 sudo mkdir -p /usr/local/src /opt/bench/bin sudo tar -xzf llcbench.tar.gz -C /usr/local/src cd /usr/local/src/llcbench ls -F第一條命令只是預(yù)覽確認(rèn)壓縮包里是有一個(gè)頂層目錄還是文件直接鋪開鋪開的情況要加--one-top-level解到子目錄里否則幾十個(gè)文件撒進(jìn)/usr/local/src很難收拾。第二條解完之后ls -F看看有哪些子目錄通常能看到和幾個(gè)模塊同名的目錄外加 Makefile 和說明文件。這時(shí)候先別急著make把gcc --version、uname -r、lscpu的摘要一起寫進(jìn)/opt/bench/README.env以后復(fù)現(xiàn)結(jié)果全靠這份記錄。3. 編譯 cachebenchMakefile 目標(biāo)選擇與 CFLAGS 取舍3.1 頂層 Makefile 的平臺(tái)目標(biāo)怎么讀這類老牌 benchmark 包的頂層 Makefile 是手寫的平臺(tái)目標(biāo)按操作系統(tǒng)命名Linux 下常見的是linux、linux-x86_64這兩種寫法年份不同、維護(hù)者不同目標(biāo)名會(huì)變。直接make大多數(shù)情況會(huì)因?yàn)闆]指定目標(biāo)而只編譯第一個(gè)規(guī)則出來的東西未必是你想要的。先讀 Makefile 頭部比盲試快得多。cd /usr/local/src/llcbench # 看前 80 行平臺(tái)目標(biāo)一般都在最上面 sed -n 1,80p Makefile # 看看有哪些 target 可選有 help 目標(biāo)就省事了 make help 2/dev/null || grep -nE ^[a-zA-Z0-9_.-]: Makefile | head -40sed那一行是為了看清變量定義重點(diǎn)找CC、CFLAGS、ARCH這幾個(gè)它們決定編譯時(shí)實(shí)際傳了什么。第二條是退路老包不一定有help目標(biāo)直接 grep 出所有規(guī)則名更實(shí)在。找到目標(biāo)后按下面的方式傳參用命令行的CC/CFLAGS覆蓋掉 Makefile 里的默認(rèn)值這是最不容易出錯(cuò)的做法。# 用命令行變量覆蓋避免去改源碼里的 Makefile make CCgcc CFLAGS-O2 -fno-omit-frame-pointer -D_GNU_SOURCE linux-x86_64 # 換了目標(biāo)或者改了 CFLAGS一定先 clean否則 .o 會(huì)復(fù)用 make clean make CCgcc CFLAGS-O2 -fno-omit-frame-pointer linux-x86_64CCgcc顯式指定編譯器是為了防止某些發(fā)行版把cc軟鏈到 clang編譯能過但生成的代碼特性不一樣。-O2是有意選的-O3會(huì)讓編譯器做更激進(jìn)的循環(huán)展開和向量化訪問步長被改寫測(cè)出來的就不是你寫的那條訪存路徑了。-fno-omit-frame-pointer是給后面 perf 采樣留后路少了它調(diào)用棧會(huì)斷。clean那一步是血淚經(jīng)驗(yàn)Makefile 的依賴關(guān)系經(jīng)常漏寫頭文件改了 CFLAGS 不 clean鏈接進(jìn)去的還是舊目標(biāo)文件你會(huì)以為參數(shù)沒生效。3.2 單模塊編譯繞過頂層 Makefile 更省事頂層目標(biāo)對(duì)不上你的發(fā)行版時(shí)別跟 Makefile 較勁直接進(jìn)模塊目錄編譯單文件。cachebench 是單文件程序一條 gcc 就能出二進(jìn)制比調(diào)試 Makefile 快。先ls確認(rèn)源文件名別照抄我這里的名字。cd /usr/local/src/llcbench/cachebench ls *.c # -O2 保持循環(huán)結(jié)構(gòu)-static 避免目標(biāo)機(jī) glibc 版本不一致-lm 是數(shù)學(xué)庫 gcc -O2 -fno-omit-frame-pointer -D_GNU_SOURCE \ -o cachebench cachebench.c -lm -static # 編完立刻驗(yàn)證二進(jìn)制能不能跑、依賴是否干凈 file cachebench ldd cachebench || echo static, no dynamic deps ./cachebench -h 21 | head -30參數(shù)逐個(gè)說清-D_GNU_SOURCE是為了讓clock_gettime、getopt_long這些接口在嚴(yán)格標(biāo)準(zhǔn)模式下也可見缺了它某些 glibc 版本會(huì)報(bào)隱式聲明。-lm一定要放在源文件后面鏈接器從左到右解析放前面會(huì)報(bào)undefined reference to sqrt。-static不是必須的但如果編譯機(jī)和測(cè)試機(jī)不是同一臺(tái)強(qiáng)烈建議加上動(dòng)態(tài)鏈接的 glibc 版本差異會(huì)讓二進(jìn)制在目標(biāo)機(jī)上直接起不來。最后那條-h是必須做的不同年份的包c(diǎn)achebench 的命令行開關(guān)不完全一樣先看它自己打印的用法后面所有命令都按這個(gè)輸出調(diào)整別硬套別人的參數(shù)。3.3 mpbench 與 blasbench 的附加編譯參數(shù)這兩個(gè)模塊屬于“有就順手編沒有也不影響主流程”。mpbench 走 MPI編譯器和鏈接器都要換成mpiccblasbench 要鏈接 BLAS庫名在不同發(fā)行版上不一樣這是最容易卡住的地方。# mpbench用 mpicc 而不是 gcc頭文件和鏈接參數(shù)它自己帶 cd /usr/local/src/llcbench/mpbench ls *.c mpicc -O2 -o mpbench mpbench.c -lm # blasbench庫名按發(fā)行版二選一找不到就查一下 cd /usr/local/src/llcbench/blasbench gcc -O2 -o blasbench blasbench.c -lopenblas -lm # Debian 系也可能是 -lblasRHEL 上 OpenBLAS 常在 /usr/lib64/openblasmpicc本質(zhì)上是個(gè)包裝腳本會(huì)把 include 路徑和-lmpi都補(bǔ)上所以不要自作聰明改成gcc -lmpiOpenMPI 的庫路徑經(jīng)常不在默認(rèn)搜索路徑里。blasbench 編譯失敗基本只有一種報(bào)錯(cuò)undefined reference to dgemm_說明 BLAS 沒鏈上先ldconfig -p | grep -i blas看到底裝了哪個(gè)實(shí)現(xiàn)再?zèng)Q定用-lopenblas還是-lblas如果庫在非標(biāo)準(zhǔn)路徑補(bǔ)一個(gè)-L/usr/lib64/openblas。編完把三個(gè)二進(jìn)制統(tǒng)一收進(jìn)/opt/bench/bin源碼目錄就不再動(dòng)了。install -m 0755 cachebench mpbench blasbench /opt/bench/bin/ 2/dev/null ls -l /opt/bench/bininstall -m 0755比cp更直接權(quán)限一步到位2/dev/null是容忍某個(gè)模塊沒編出來的情況比如你沒裝 MPImpbench 不存在不影響另外兩個(gè)。4. 讓數(shù)字可復(fù)現(xiàn)頻率、NUMA 與 THP 的三處配置4.1 頻率與 C-state別讓 CPU 在測(cè)試中自己降頻默認(rèn)的powersave調(diào)速器會(huì)讓 CPU 頻率跟著負(fù)載浮動(dòng)緩存帶寬和核心頻率強(qiáng)相關(guān)頻率一動(dòng)同一段代碼跑兩次能差出三成。這不是玄學(xué)是電源管理在正常工作只是和測(cè)量的目標(biāo)沖突。# 看當(dāng)前策略和支持的策略 cpupower frequency-info | grep -E driver|governor|available cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 全部核心切到 performance sudo cpupower frequency-set -g performance # 關(guān)掉睿頻intel_pstate 和 acpi-cpufreq 路徑不同兩個(gè)都試 cat /sys/devices/system/cpu/intel_pstate/no_turbo 2/dev/null echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo 2/dev/null echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost 2/dev/null # 順帶把 C-state 限制到 C1避免深睡喚醒延遲污染小工作集的測(cè)量 sudo cpupower idle-set -D 1-g performance是把下限頂?shù)缴舷薮鷥r(jià)是功耗和溫度測(cè)試機(jī)上無所謂。關(guān)睿頻這一步經(jīng)常被忽略睿頻一開單核跑分時(shí)頻率會(huì)沖到標(biāo)稱之上測(cè)出來的 L1 帶寬比理論值還高看著很爽但完全不可比。idle-set -D 1限制的是空閑狀態(tài)深度工作集只有幾十 KB 的時(shí)候CPU 大部分時(shí)間在等待緩存命中深 C-state 的進(jìn)出延遲會(huì)體現(xiàn)在結(jié)果里。這幾條改的都是運(yùn)行時(shí)狀態(tài)重啟即失效所以不用怕改壞但也意味著每次重啟后要重新執(zhí)行。4.2 NUMA 與綁核單線程跑分最怕遷移雙路機(jī)器上跨 NUMA 節(jié)點(diǎn)訪存的帶寬可能只有本地的一半多一點(diǎn)。cachebench 是單線程程序如果內(nèi)核調(diào)度器在測(cè)試過程中把它從這個(gè)節(jié)點(diǎn)挪到那個(gè)節(jié)點(diǎn)前半段和后半段測(cè)的根本不是同一塊內(nèi)存曲線會(huì)出現(xiàn)莫名其妙的抖動(dòng)。# 先看清節(jié)點(diǎn)布局和每個(gè)節(jié)點(diǎn)的本地內(nèi)存大小 numactl -H # 綁到 0 號(hào)節(jié)點(diǎn)的 CPU 和內(nèi)存兩邊都鎖死 numactl --cpunodebind0 --membind0 /opt/bench/bin/cachebench -b -m 512 # 沒有 numactl 時(shí)至少把進(jìn)程釘在某個(gè)核上 taskset -c 2 /opt/bench/bin/cachebench -b -m 512--cpunodebind管 CPU--membind管內(nèi)存分配兩個(gè)都要寫。只寫前者內(nèi)存仍可能從遠(yuǎn)端節(jié)點(diǎn)分配測(cè)出來的帶寬照樣偏低。taskset -c 2是更輕量的替代只保證不遷移不保證內(nèi)存在本地適合已經(jīng)在 BIOS 里關(guān)掉節(jié)點(diǎn)交織的機(jī)器。判斷有沒有綁對(duì)跑的時(shí)候另開一個(gè)窗口執(zhí)行ps -o psr,comm -p $(pgrep cachebench)看PSR那一列是不是一直不變。4.3 大頁、swap 與 page cache把內(nèi)存?zhèn)鹊脑肼晧旱敉该鞔箜摃?huì)讓同一份工作集有時(shí)候落在 4K 頁、有時(shí)候落在 2M 頁頁表項(xiàng)數(shù)量和 TLB 命中率完全不同這就是同一臺(tái)機(jī)器兩次結(jié)果不一致的常見來源。測(cè)緩存的時(shí)候要的是確定性的頁行為寧可關(guān)掉。# 關(guān)閉 THP讓所有分配都走 4K 頁行為可預(yù)測(cè) echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag # 關(guān)掉 NUMA balancing它會(huì)周期性掃描并遷移頁面 echo 0 | sudo tee /proc/sys/kernel/numa_balancing # 測(cè)試期間不需要 swap避免偶發(fā)換出拖慢大工作集那一段 sudo swapoff -a # 跑之前清一次 page cache保證大數(shù)組是真正的冷內(nèi)存 sync echo 3 | sudo tee /proc/sys/vm/drop_cachesdefrag那一項(xiàng)和enabled要一起關(guān)只關(guān)一個(gè)的情況下內(nèi)核仍可能為了湊大頁去做內(nèi)存整理表現(xiàn)為測(cè)試中途出現(xiàn)幾百毫秒的停頓。drop_caches寫 3 表示同時(shí)清 page cache、dentries 和 inodes需要 root且只在測(cè)試開始前執(zhí)行一次不要在循環(huán)里反復(fù)清那樣會(huì)把測(cè)量變成 IO 測(cè)試。4.4 一份可復(fù)用的測(cè)試前腳本與還原腳本上面這些零散命令每次手敲三遍必出錯(cuò)。寫成兩個(gè)腳本一個(gè)負(fù)責(zé)進(jìn)入測(cè)試態(tài)一個(gè)負(fù)責(zé)還原跑完一定執(zhí)行還原否則這臺(tái)機(jī)器后面跑什么都偏慢。#!/usr/bin/env bash # /opt/bench/prep.sh —— 進(jìn)入測(cè)試態(tài)需要 root set -euo pipefail for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -w $f ] echo performance $f # 逐核設(shè)置別指望一條命令全生效 done echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag echo 0 /proc/sys/kernel/numa_balancing # 停止頁面自動(dòng)遷移 swapoff -a || true # 已關(guān)時(shí)返回非零用 || true 兜住 sync echo 3 /proc/sys/vm/drop_caches echo [prep] done at $(date -Is)set -euo pipefail讓腳本在任一步失敗時(shí)立刻停下避免只配了一半還以為配好了。逐核寫scaling_governor而不是只用 cpupower是因?yàn)槟承﹥?nèi)核上 cpupower 只改 policy 對(duì)象個(gè)別核心會(huì)被漏掉。|| true只加在swapoff上因?yàn)樗鼘?duì)已關(guān)閉的 swap 會(huì)返回非零這是預(yù)期行為不該中斷腳本。#!/usr/bin/env bash # /opt/bench/restore.sh —— 還原跑完立刻執(zhí)行 set -uo pipefail for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -w $f ] echo schedutil $f # 按發(fā)行版默認(rèn)策略改ondemand 也行 done echo madvise /sys/kernel/mm/transparent_hugepage/enabled echo 1 /proc/sys/kernel/numa_balancing echo [restore] done at $(date -Is)還原腳本里沒去動(dòng) swap 和睿頻因?yàn)檫@兩項(xiàng)在多數(shù)生產(chǎn)機(jī)上本來就不該由測(cè)試腳本隨意改動(dòng)手工確認(rèn)更穩(wěn)妥。schedutil在較新內(nèi)核上是默認(rèn)策略老內(nèi)核用ondemand改之前先看一眼/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors里有什么別寫一個(gè)不存在的策略名進(jìn)去。5. 避坑排查llcbench 編譯與運(yùn)行的五類翻車5.1 編譯期目標(biāo)不存在和頭文件找不到現(xiàn)象一make linux-x86_64報(bào)No rule to make target。原因這份包的 Makefile 年代較早平臺(tái)目標(biāo)命名和你猜的不一樣可能只寫了linux也可能要求通過變量傳架構(gòu)。解決grep -nE ^[a-z].*: Makefile把所有規(guī)則列出來照著用實(shí)在沒有匹配的放棄頂層 Makefile進(jìn)模塊目錄按 3.2 節(jié)的方式單文件編譯兩分鐘的事別在這里耗。現(xiàn)象二fatal error: mpi.h: No such file or directory但明明裝了 OpenMPI。原因openmpi-devel在 RHEL 系上裝到/usr/lib64/openmpi/頭文件和mpicc都不進(jìn)默認(rèn)搜索路徑。解決用mpicc編譯而不是gcc或者先module load mpi/openmpi-x86_64Debian 系一般沒這個(gè)問題因?yàn)閘ibopenmpi-dev會(huì)把頭文件放進(jìn)/usr/include。5.2 運(yùn)行期段錯(cuò)誤、全零輸出和虛高帶寬現(xiàn)象三二進(jìn)制一跑就Segmentation fault編譯時(shí)毫無警告。原因老代碼里對(duì)long和指針寬度的假設(shè)在 64 位下不成立或者工作集上限-m給得太大、超過了容器或 cgroup 的內(nèi)存限制被 OOM killer 干掉。解決先dmesg | tail -20看有沒有 oom 記錄有就把-m降到物理內(nèi)存的六成以內(nèi)沒有就加-g重編一版用gdb ./cachebench跑出棧定位到具體行?,F(xiàn)象四輸出表里所有尺寸的帶寬都是同一個(gè)數(shù)量級(jí)或者干脆全是 0。原因多半是編譯優(yōu)化過頭。-O3加上-marchnative時(shí)編譯器可能識(shí)別出循環(huán)里的訪存模式?jīng)]有副作用直接把整段消掉計(jì)時(shí)器測(cè)到的是一個(gè)空循環(huán)于是輸出一個(gè)巨大且平坦的值。解決退回-O2去掉-marchnativemake clean后重編。判斷是不是被優(yōu)化掉objdump -d cachebench | grep -c mfence之類的指令計(jì)數(shù)只是個(gè)參考最直接的辦法是看跑的時(shí)候top里 CPU 占用是不是接近 100%低于這個(gè)數(shù)基本可以確定循環(huán)沒真正執(zhí)行。5.3 結(jié)果期曲線沒拐點(diǎn)、兩次差三成現(xiàn)象五帶寬曲線是一條平滑下降的直線看不到 L1/L2/L3 的臺(tái)階。原因三條可能工作集上限-m太小沒越過 LLCTHP 沒關(guān)導(dǎo)致頁行為不一致把臺(tái)階抹平了或者進(jìn)程每次都重新分配內(nèi)存、測(cè)的其實(shí)是頁錯(cuò)誤開銷。解決把-m設(shè)成 LLC 容量的 816 倍比如 LLC 32 MB 就給-m 512確認(rèn)第 4 章的腳本執(zhí)行過跑之前清一次 page cache?,F(xiàn)象六同一臺(tái)機(jī)器、同一份二進(jìn)制兩次跑差 30% 以上。原因頻率沒鎖、進(jìn)程被調(diào)度到別的 NUMA 節(jié)點(diǎn)、或者機(jī)器上還有別的進(jìn)程在搶 LLC。解決按 4.1 和 4.2 節(jié)鎖頻綁核跑之前ps aux --sort-%cpu | head確認(rèn)沒有別的重負(fù)載兩次結(jié)果差異用標(biāo)準(zhǔn)差衡量綁核綁對(duì)了通常能壓到 3% 以內(nèi)壓不下去就說明還有別的東西在動(dòng)。6. 裝完怎么驗(yàn)證跑通三組數(shù)字對(duì)上了才算裝完裝完不等于跑通。判斷標(biāo)準(zhǔn)很簡單cachebench 的輸出、perf stat的計(jì)數(shù)、硬件的理論值這三組能互相解釋才算這套環(huán)境搭成功了。只看 cachebench 那一張表很容易被虛高的數(shù)字騙過去。先跑一遍基準(zhǔn)把輸出存下來# -b 跑完整掃描-m 給到 LLC 的 8~16 倍-i 提高迭代次數(shù)降低抖動(dòng) /opt/bench/bin/cachebench -b -m 512 | tee /opt/bench/run-$(date %Y%m%d-%H%M).log看這張表只做三件事。第一找臺(tái)階從幾 KB 到幾十 KB 這一段應(yīng)該是最高帶寬越過 L1 掉一檔越過 L2 再掉一檔越過 LLC 掉到內(nèi)存水平臺(tái)階位置要和你 2.1 節(jié)抄下來的緩存尺寸對(duì)得上差一個(gè)數(shù)量級(jí)說明參數(shù)或環(huán)境有問題。第二看讀寫差異同尺寸下讀帶寬通常高于寫如果寫反而更高多半是寫路徑被編譯器優(yōu)化成了非臨時(shí)存儲(chǔ)回去檢查 CFLAGS。第三看尾部是否收斂工作集超過物理內(nèi)存的六成以后曲線應(yīng)該趨于平緩還在往下掉說明已經(jīng)在換頁了。再用 perf 交叉驗(yàn)證一次確認(rèn)測(cè)的就是緩存行為# perf_event_paranoid 太嚴(yán)會(huì)拒絕非 root 采樣先放開到 1 echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid # 只跑一個(gè)中等工作集看 LLC 命中率是否隨尺寸變化 sudo perf stat -e cycles,instructions,cache-references,cache-misses,LLC-loads,LLC-load-misses \ /opt/bench/bin/cachebench -b -m 512cache-misses除以cache-references得到的失效率應(yīng)該隨著工作集增大單調(diào)上升LLC-load-misses在越過 LLC 之后會(huì)明顯抬升。如果失效率從頭到尾是平的說明 perf 采樣到的和 cachebench 實(shí)際訪問的不是同一段內(nèi)存回頭檢查numactl有沒有寫錯(cuò)節(jié)點(diǎn)。最后拿理論值對(duì)一下量級(jí)單通道 DDR 的實(shí)測(cè)帶寬通常在十幾 GB/s 這個(gè)量級(jí)L1 能到上百 GB/sL3 介于兩者之間實(shí)測(cè)值低于理論峰值三成以內(nèi)都算正常差一個(gè)數(shù)量級(jí)就別急著寫報(bào)告。觀察點(diǎn)正常表現(xiàn)異常時(shí)先查什么臺(tái)階位置與 lscpu 緩存尺寸一致-m上限、THP 是否關(guān)讀 vs 寫讀帶寬更高CFLAGS 是否-O3尾部收斂超過內(nèi)存六成后走平是否觸發(fā)換頁、-m過大perf 失效率隨工作集單調(diào)上升綁核、NUMA 節(jié)點(diǎn)從那以后我每次把 llcbench 解到一臺(tái)新機(jī)器上都強(qiáng)制先跑lscpu抄底賬、執(zhí)行一遍 prep 腳本鎖頻綁核、再用 perf 對(duì)一次失效率三步缺一步就不記錄數(shù)字。這個(gè)習(xí)慣救過我兩次一次是發(fā)現(xiàn)機(jī)器 BIOS 里睿頻根本沒關(guān)另一次是發(fā)現(xiàn)容器只給了 1 GB 內(nèi)存-m 512直接撞上了 cgroup 上限。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取