軟件性能之CPU

性能是個宏大而駁雜話題,從代碼,到網絡,到實施,方方面面都會涉及到性能問題,網上對性能講解的文章多如牛毛,從原理到方法再到工具都有詳細的介紹,本文雖不能免俗,但期望能從另外一個角度來觀察性能:站在硬件的角度看待軟件性能。
要探討高性能,首先要明確一下,什么是高性能? 是每秒過億的QPS?還是每秒數G的數據傳輸?這些當然都是高性能的量化指標,但我想再更本質得明確一下高性能的含義,其核心訴求點在于:將硬件資源利用到核心的業務邏輯上,展開來講:
  1. 資源利用率:程序能充分的利用硬件資源,到達得性能上限應該是硬件上限而非軟件上限。
  2. 資源能效:完成相同的工作使用更少的資源,硬件資源使用在核心業務邏輯上。
那么如何實現高性能?
軟件行業常有一個傳言:過早的性能優化是萬惡之源。但我想應該額外再補充一句:早期的性能考慮是高效之基。應該在編寫第一行代碼之前就想好怎么樣的設計是高性能,而非等到功能完善之后再回頭進行優化。心中時刻謹記高性能,在每個細節處都精益求精,才能真正的實現高性能。前期不管不顧,后期再性能優化面臨幾個問題:
  1. 有些程序結構不合理導致的性能問題變更起來牽連甚廣,修改成本太高,不優化又不行
  2. 沒有集中的性能問題,各處邏輯時間消耗不長也不短,最終性能不低也不高,卻無處著手
這里分別從不同的硬件出發,來探討如何壓榨硬件資源,追求極致性能,本篇文章是第一篇,CPU篇。

CPU構造

現代CPU架構是一個高度復雜且多層次優化的系統,其設計核心目標是 最大化計算效率 資源利用率 。可以簡單的概括其幾個特性:
  1. 多核設計:現代CPU通常包含多個物理核心,每個核心可以獨立的執行指令。單個物理核心通過虛擬化技術模擬多個邏輯核心,共享核心的運算單元和緩存,實現超線程。
  2. 指令流水線:將指令執行拆分為多個階段(取指,解碼。執行,訪存,寫回), 各個階段可以并行工作,以提升吞吐量,數據依賴或分支預測失敗會導致流水線停滯
  3. 多級緩存:L1緩存分指令緩存(L1i)和數據緩存(L1d),每個核心獨享,延遲1-3周期。L2緩存:核心獨享或共享,延遲約10周期。L3緩存:多核心共享,延遲約30-50周期。多層緩存逐級擴大。通過標記緩存行的狀態,確保多核間數據一致性,避免臟讀。
這是一個簡單的多級緩存多核CPU架構圖
有了這些簡單的CPU知識,我們就可以進一步討論如何充分得利用CPU的這些特性以實現高性能。

有效利用多核性能

大家知道現代操作系統調度CPU時間片的單位是線程,所以想充分利用多核特性,就要開啟多個線程。
每個線程在Linux系統中都被封裝為一個task,每個task包含了自己的堆棧,cpu執行寄存器的上下文(這是線程能夠被搶占切換調度的關鍵),以及一些其他信息。從原理上講,應該使用和CPU核心數量相當的線程數量,但由于應用程序和CPU之間并非直接的使用關系,中間還有一層調度層,即操作系統。操作系統內核也會有自己的執行進程。所以早期Windows核心編程書中推薦使用cpu核心數*2的線程配置。但不論怎么設置線程數量,都不宜過多或者過少。操作系統需要保證公平且高效的調度讓所有線程都有機會在CPU上得到執行,但線程有CPU密集的計算邏輯,也有和外部設備進行數據通信的IO邏輯,如何給不同的線程分配CPU呢?這又是一套很復雜的算法,簡單來歸納:CPU密集的線程被調度的次數少,但是執行的時間片長,IO密集型的線程被調度的次數多,但是執行的時間片短。開的線程越多,那么操作系統執行算法需要調度的對象就越多。開的線程太少,則無法有效利用多核特性,自不待言。
操作系統是如何測量線程到底是IO密集還是CPU密集的呢?答案是:操作系統并不能明確的知道。因為IO密集和CPU密集并不是絕對屬性,CPU計算的過程中可能會發生IO, 而IO的過程中也會有CPU計算,操作系統只能根據一些操作統計來推斷線程行為。這導致一個問題,當一個線程阻塞在一個IO操作上,比如阻塞的向一個socket發送數據,我們知道這個調用send的過程可能很短,只是把用戶態的內存拷貝進內核態的緩存中,也可能很長,需要等待內核協議棧將內核緩存的數據發送,留出足夠的空閑以容納新發送的數據。站在應用程序的角度看,就是send發送阻塞了線程的執行。這個阻塞的過程,操作系統是知道的,其會將線程標記為block狀態之后讓出CPU,問題是操作系統什么時候知道了send已經完成,需要將這個線程切回到CPU上繼續執行呢?這需要硬件觸發中斷來再次觸發操作系統的調度邏輯,如果只是簡單的調用阻塞的IO接口,那么什么時候切換回CPU執行只能依賴于操作系統的調度策略,時間可長可斷,但并不會是一個高效的時機。如果要充分提升CPU的使用率,就要避免使用阻塞式的系統調用,現代操作系統一般都提供了異步響應式的編程接口,將在到另外一篇文章中討論。
總的來說,開多少個線程合適并不是一個簡單的問題,要結合自己的硬件水平,執行邏輯等多方面因素,因地制宜的綜合決策。

有效利用緩存

CPU和主存之間每次交換數據需要近百的CPU時間,如果每次數據交互都需要直接訪問主存,CPU大多數時間都將消耗在數據等待上。CPU提供的三層緩存機制:第一級最接近CPU核心,訪問速度最快,之后每級速度遞減,一二級緩存通常單個核心獨享,三級緩存多個核心共享,其內部也有自己的數據一致性算法。感興趣的讀者可以自行搜索,本文不再討論。
每個線程在CPU看來,無非是一堆數據以及對應的計算指令,想要提升緩存的利用率,其實和常規的編程過程是一樣的:提升緩存命中率,降低緩存miss。不同的點在于平常的內存操作是顯式的,我們明確的知道哪里訪問了緩存,哪里發生了miss,CPU的緩存命中與否,并沒有那么直觀。CPU每次進行緩存交換的單位,稱為cache line,每個cache line通常為64字節。從編程的角度出發,可以通過以下措施提升緩存的命中率:
  • 避免頻繁的線程切換。每個線程都有自己的運行上下文,對CPU來講就是不同的數據和指令,所以每次在CPU上切換線程,之前緩存的其他線程數據大概率會失效,進而導致需要重新從低訪問速度的緩存或者內存中加載數據。現代操作系統都提供了API來實現線程和CPU核心的綁定,支持一個線程一直都被調度到一個CPU核心上,從而提升一二級緩存的命中率。
  • 盡量訪問內存相鄰的數據,以提高cache line的數據使用率,減少緩存數據交換次數。比如我們有個二維數據:
縱向的不同顏色的列表示內存連續的第二維數組,很常見的數據結構。遍歷這個二維數組,當橫向遍歷時,每次訪問的都是不連續的內存,當 j 的序列超過一個cache line的時候,意味著每次訪問都需要加載一個新的cache line才能讀取到新的數據。當縱向的遍歷時,每次訪問的內存位置是連續的,所以一個cache line上大部分數據都會被利用到。雖然在代碼層面來看無論怎么遍歷結果都是相同的,但對CPU來講,縱向的遍歷能顯著的減少cache line的交換次數,可以大幅提升CPU的吞吐量。
  • 避免局部數據的多線程訪問。我們都知道多個線程讀寫相同的變量會導致內存競爭問題,需要進行加鎖。那我們在不同線程訪問相鄰的變量時會發生什么?
struct Data { int32_t param_a_;int32_t param_b_;
};
當兩個線程分別讀寫這個結構體的param_a_,param_b_,由于cache line是64字節,而CPU更新緩存的單位只有cache line。所以當A線程訪問param_a_時,可能會將param_b_也一并加載,線程B訪問param_b_也是相同的道理,當CPU的某個核心修改了cache line的數據,而這個cache line又被其他核心訪問,為了防止數據不一致的情況,CPU會將這個cache line失效,以加載最新的數據,這會導致大量的cache line交換。為了避免這種情況的發生,我們需要用一些編程技巧來進行CPU的數據訪問隔離。可以使用內存對齊,將不同線程訪問的變量按照64字節對齊。或者使用thread local store來降低線程間的數據競爭,當然這會比中心式的數據維護要復雜很多。

提升分支預測準確度

結構化編程最常見的編程結構便是分支控制,我們的代碼中充斥的大量的if else,平時編程的時候多一個少一個,怎么排布順序對程序功能來說無傷大雅。但站在CPU的角度上看,由于分支預測失效會導致數十乃至數百CPU時鐘的停頓,提升分支預測的準確性就可以有效的提升CPU的指令執行效率。
好在我們依然可以使用一些簡單的編程策略來提升分支準確度:
  • 減少代碼的判斷分支,合并條件判斷。這是最直觀的降低分支預測的失敗的方法。
  • 優化分支模式,使分支判斷具有局部性。比如遍歷一個整數數組,偶數執行A操作,奇數執行B操作,如果數據按照先偶數再奇數的方式排列,就可以有效提升分支預測準確度
  • 優先處理高頻路徑,將最可能執行的分支判斷放到最上方
  • 避免使用復雜的分支判斷,將大量的if else通過查找表實現。
性能優化是個博大精深的問題,本文從CPU的角度出發,介紹了一些性能優化的知識,但知距離全面詳實還差的很遠。本篇也作為性能問題的一個開篇,之后計劃再介紹一下IO的性能優化手段。
Finally, 期望本文能對大家有所啟發,你還知道什么特別的CPU利用率的優化知識,也歡迎交流討論。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83216.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83216.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83216.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[SC]SystemC在CPU/GPU驗證中的應用(三)

SystemC在CPU/GPU驗證中的應用(三) 摘要:下面分享50個逐步升級SystemC編程能力的示例及建議的學習路線圖。您可以一次一批地完成它們——從前五個基礎的例子開始,然后轉向channels, TLM, bus models, simple CPU/GPU kernels等等。在每個階段掌握之后,再進行下一組…

如何設計高效的數據湖架構:存儲策略、Schema 演進與數據生命周期管理

本文圍繞現代數據湖架構的核心設計理念與實踐展開,重點討論如何高效組織數據存儲、支持 Schema 演進與版本管理、實現冷熱數據分層存儲和生命周期治理,確保數據湖在性能、成本、演進和治理能力上的全面可控。 ?? 一、數據湖架構演進概覽 傳統數據倉庫面對高頻更新、Schema…

建筑兔零基礎人工智能自學記錄101|Transformer(1)-14

Transformer 谷歌提出,一組編碼-解碼器 可以同時處理,通過位置編碼來處理單詞 實質是token詞語接龍(只是有不同的概率) token對應向量 Transformer簡述 文生圖就需要用到transformer黑箱 token 內部層次 中間主要是embedding…

Unity基礎學習(十二)Unity 物理系統之范圍檢測

目錄 一、關于范圍檢測的主要API: 1. 盒狀范圍檢測 Physics.OverlapBox 2. 球形范圍檢測 Physics.OverlapSphere 3. 膠囊范圍檢測 Physics.OverlapCapsule 4. 盒狀檢測 NonAlloc 版 5. 球形檢測 NonAlloc 版 6. 膠囊檢測 NonAlloc 版 二、關于API中的兩個重…

構建安全高效的郵件網關ngx_mail_ssl_module

一、快速上手:最小配置示例 worker_processes auto;mail {server {# 監聽 IMAP over TLSlisten 993 ssl;protocol imap;# TLS 協議與密碼套件ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers HIGH:!aNULL:!MD5;# 證書與私鑰ssl_…

打卡day41

知識回顧 數據增強卷積神經網絡定義的寫法batch歸一化:調整一個批次的分布,常用與圖像數據特征圖:只有卷積操作輸出的才叫特征圖調度器:直接修改基礎學習率 卷積操作常見流程如下: 1. 輸入 → 卷積層 → Batch歸一化層…

MySQL高級查詢技巧:分組、聚合、子查詢與分頁【MySQL系列】

本文將深入探討 MySQL 高級查詢技巧,重點講解 GROUP BY、HAVING、各種聚合函數、子查詢以及分頁查詢(LIMIT 語法)的使用。文章內容涵蓋實際應用中最常見的報表需求和分頁實現技巧,適合有一定 SQL 基礎的開發者進一步提升技能。 一…

現代 CSS 高階技巧:實現平滑內凹圓角的工程化實踐

通過 數學計算 CSS mask 復合遮罩 實現的真正幾何內凹效果: 背景是一張圖片,用來證明中間的凹陷是透明的。 完整代碼: app.js import FormPage from "./pages/formPage"; import "./App.css"; const App () > {re…

Qt不同布局添加不同控件

對于這種 不同布局添加不同控件 的情況,可以采用以下幾種簡化方法: 方法 1:使用 std::pair 或 std::tuple 配對(C++17 推薦) for (auto [layout, widget] : {std::pair{m_layoutMistakeCalibrate,

MySQL 事務解析

1. 事務簡介 事務(Transaction) 是一組操作的集合,它是一個不可分割的工作單位,事務會把所有的操作作為一個整體一起向系統提交或撤銷操作請求,即這些操作要么同時成功,要么同時失敗。 經典案例&#xff1…

PyTorch中 torch.utils.data.DataLoader 的詳細解析和讀取點云數據示例

一、DataLoader 是什么? torch.utils.data.DataLoader 是 PyTorch 中用于加載數據的核心接口,它支持: 批量讀取(batch)數據打亂(shuffle)多線程并行加載(num_workers)自…

在MDK中自動部署LVGL,在stm32f407ZGT6移植LVGL-8.4,運行demo,顯示label

在MDK中自動部署LVGL,在stm32f407ZGT6移植LVGL-8.4 一、硬件平臺二、實現功能三、移植步驟1、下載LVGL-8.42、MDK中安裝LVGL-8.43、配置RTE4、配置頭文件 lv_conf_cmsis.h5、配置lv_port_disp_template 四、添加心跳相關文件1、在STM32CubeMX中配置TIM7的參數2、使能…

德思特新聞 | 德思特與es:saar正式建立合作伙伴關系

德思特新聞 2025年5月9日,德思特科技有限公司(以下簡稱“德思特”)與德國嵌入式系統專家es:saar GmbH正式達成合作伙伴關系。此次合作旨在將 es:saar 的先進嵌入式開發與測試工具引入中國及亞太市場,助力本地客戶提升產品開發效率…

fork函數小解

學了好久終于搞懂fork函數的一些作用 1. fork函數作用:用于創建新的子進程 這是fork最根本的功能,在父進程里創建新的子進程、 但是創建新的子進程之后呢? 子進程和父進程的關系是什么樣的? 為什么fork得到的子進程返回值為0&am…

opencv(C++) 變換圖像與形態學操作

文章目錄 使用腐蝕和膨脹圖像形態濾波器實現案例使用形態學濾波器對圖像進行開運算和閉運算實現案例在灰度圖像上應用形態學操作算子形態學梯度(Morphological Gradient)黑帽變換(Black-hat Transform)使用分水嶺算法進行圖像分割使用 MSER 提取顯著區域MSER 檢測與可視化使…

測試工程師學LangChain之promptTemplate 實戰筆記

一、引言:大模型時代的測試自動化革命 2025 年,隨著大模型(如 DeepSeek)在自動化測試領域的廣泛應用,Prompt 編寫已成為測試工程師的核心技能之一。 為什么? 大模型輸出的質量 90% 取決于輸入的 PromptLangChain 的 PromptTemplate 提供了參數化 Prompt 的標準化方案Ope…

CP2K 軟件介紹與使用指南

CP2K 軟件介紹與使用指南 一、CP2K簡介 CP2K是一款開源的量子化學和固態物理模擬軟件包,主要用于原子尺度模擬,特別擅長以下領域: 第一性原理計算:基于密度泛函理論(DFT)的電子結構計算分子動力學(MD):包括從頭算分…

npm、pnpm、yarn使用以及區別

npm 使用 安裝包&#xff1a;在項目目錄下&#xff0c;npm install <包名> 用于本地安裝包到 node_modules 目錄&#xff0c;并添加到 package.json 的 dependencies 中&#xff1b;npm install -g <包名> 用于全局安裝&#xff0c;適用于命令行工具等。初始化項目…

2025年北京市職工職業技能大賽第六屆信息通信行業網絡安全技能大賽復賽CTF部分WP-哥斯拉流量分析

2025年北京市職工職業技能大賽第六屆信息通信行業網絡安全技能大賽復賽CTF部分WP-哥斯拉流量分析 一、流量分析 題目沒有任何提示,附件gzl.pcap 解題哥斯拉流量300多KB包很多,沒啥經驗只能挨個看回來之后又狠狠得擼了一把哥斯拉流量分析我這里用的是哥斯拉4.0.1 測試鏈接…

GitLab 18.0 正式發布,15.0 將不再受技術支持,須升級【六】

GitLab 是一個全球知名的一體化 DevOps 平臺&#xff0c;很多人都通過私有化部署 GitLab 來進行源代碼托管。極狐GitLab 是 GitLab 在中國的發行版&#xff0c;專門為中國程序員服務。可以一鍵式部署極狐GitLab。 學習極狐GitLab 的相關資料&#xff1a; 極狐GitLab 官網極狐…