倉庫:https://gitee.com/mrxiao_com/2d_game_2
黑板:優化的基本過程
在游戲編程中,優化是一個非常重要的學習內容,尤其是想要成為專業開發者時。優化的核心是理解代碼的執行速度,以及如何提升其性能。在這個階段,已經開始討論如何有效地進行優化。
首先,需要收集統計數據,這是優化的第一步。收集數據的目的是為了了解程序運行緩慢的地方,找出具體的瓶頸,并分析這些部分的特點,比如它們處理了多少內容、執行的頻率如何等。這些信息將幫助確定哪些代碼部分需要被優化。
接下來,基于收集到的統計數據,進行估算。估算的目的是根據現有數據,推測出代碼應該達到的理想性能。盡管很難做到準確,但可以通過估算得到一個大致的參考值,了解當前性能與理想性能之間的差距。
然后,進行效率和性能分析。效率和性能是兩個不同的概念,效率指的是代碼實際做了多少工作,而性能則是指完成這些工作的速度。提高效率意味著減少實際需要做的工作量,而提高性能則是讓這些工作通過CPU更快速地完成。
一旦分析了效率和性能的差異,就可以判斷出優化的潛力。最后,根據分析結果,開始編寫優化代碼,并測試其效果,看是否能夠提升性能。
總結來說,優化過程包含四個步驟:收集數據、進行估算、分析效率和性能、以及編寫優化代碼。通過這一過程,可以系統地改進代碼的執行效率和速度。
收集統計數據
首先要進行的是收集統計數據,這主要涉及編程的部分。我們需要開始收集一些統計信息。收集統計數據的方式是對代碼進行“圖表化”,即在代碼中添加一些內容,使其能夠報告統計數據給我們。雖然有一些商業程序可以分析代碼,如 Intel 的 vtune 等,但這里不打算使用這些工具,而是展示如何自己編寫并收集統計數據。如果進行深入優化,可能會選擇使用一些工具,像 Intel 提供的工具,或者其他地方的工具,它們能提供一些程序運行的內部信息,這些是我們通過簡單的儀表化可能無法得到的。
我們將專注于實現一個簡單的統計收集方法。首先,需要知道的是,在 CPP
中,游戲的更新和渲染是我們負責的代碼部分(即不在 Windows 系統內)。我們想要了解的是:游戲更新和渲染調用總共消耗了多少處理器周期。然后,我們還希望能看到,在其中一些代碼片段的執行過程中,分別消耗了多少處理器周期,這樣可以幫助我們大致了解哪些部分的代碼可能會影響程序的運行速度。
雖然我們已經知道渲染器目前非常慢,因為在進行相關更改時已經觀察到它的性能下降,所以此時并不一定需要重新分析渲染器部分的代碼。但為了能夠跟蹤那些性能可能不那么直觀的部分,還是決定進行一些通用的儀表化工作。這么做是為了展示如何找出性能瓶頸。實際上,如果只想盡可能快速地完成這個過程,我們可以直接獲取游戲代碼的總處理器周期數,再獲取繪制矩形慢操作的處理器周期數,然后對比兩者,直接看出慢的部分。但現在,決定采用稍微復雜一點的方法來實現這個目標。
重新審視 __rdtsc
目標是創建一個簡單的系統,能夠記錄處理器周期并計算兩個位置之間所花費的周期數。實際上,這種功能我們已經實現過一次,早在處理 Win32 平臺層時就已經有過類似的代碼,這段代碼被稱為 DTSC
。
DTSC
使用了一個處理器指令,稱為 read time stamp counter
,這是編譯器通過內嵌指令生成的匯編指令。這個指令的作用是提供一個來自處理器的計數器,表示當前處理器在指令流中的位置,換句話說,它表示處理器執行了多少個周期。需要注意的是,“周期”這個詞現在有些模糊,因為不同的處理器可能以不同的方式報告周期時間,特別是在啟用了“SpeedStep”等技術的處理器上。
不過,現代處理器通常會給出在滿速運行下的周期數,即使在啟用節能模式(如筆記本的處理器會在不同負載下調整功耗)時,可能也能給出一個相對準確的周期計數。如果處理器頻率時常變化,可能需要更小心地處理這些周期數據,但如果處理器在高性能模式下運行,假設 DTSC
提供的周期數與實際周期數接近,可以大致認為數據是準確的。
GAME_UPDATE_AND_RENDER:添加 __rdtsc 循環計數
可以像之前那樣,通過記錄開始和結束時的 __rdtsc
來計算經過的周期數。具體做法是,在函數的開始和結束處獲取時間戳計數器的值,然后通過計算這兩個值的差異來得出執行該代碼段所花費的周期數。
簡單來說,首先在代碼段開始處記錄一個起始周期計數,使用 __rdtsc
獲取初始計數值;然后在代碼段結束時記錄一個結束周期計數,同樣使用 __rdtsc
。計算這兩個值的差異,即可得到該代碼段執行所消耗的處理器周期數。
基本的實現方式是,在代碼開始時獲取開始周期數,在結束時獲取結束周期數,然后計算它們的差值。這樣就能夠得到該段代碼執行所用的總周期數。
然而,在這個過程中存在一個問題。
在 game_platform.h 中引入 debug_cycle_counter
目前,我們沒有辦法將任何信息輸出到屏幕上。雖然 Win32 代碼能夠輸出內容,但游戲本身并沒有輸出的能力,因為游戲并沒有顯示功能,甚至沒有相關的輸入輸出支持。因此,想要將統計數據輸出到 Win32 層,需要簡單地通過一些基本的方式實現。
為了實現這一點,可以做一些簡單的操作。考慮到許多性能計數器的實現有時過于復雜,這里決定采取一個非常簡單的辦法:創建一個靜態表來存儲這些計數器。實現過程非常直接,基本上只是創建一個簡單的結構來存儲周期計數。
在游戲的內存管理部分,新增一個名為 debug cycle counter
的計數器。這個計數器會是一個 uint64_t
類型,用來記錄周期數。在調試模式下,會有多個這樣的計數器。舉個例子,可以在游戲調試內存區域分配 256 個這樣的計數器。
這些計數器只是用來調試時使用的,在發布版本時可以去掉。為了避免在發布版本中仍然保留這些調試信息,可以將它們放到一個名為 game_internal
的區域,只有在調試模式下定義 game_internal
時,才會啟用這些計數器。這樣,就能在開發和發布版本之間靈活切換,確保發布版本沒有多余的調試信息。
支持 Linux 和 Mac 等平臺的用戶
為了避免對其他平臺的支持產生問題,特別是處理器不支持 __rdtsc
指令的情況,可以通過宏來實現這一功能。首先,確定編譯器是否支持 __rdtsc
,然后在符合條件的情況下,使用宏來處理代碼的開始和結束時間記錄。
具體做法是,創建一個宏,名為 BEGIN_TIMED_BLOCK
,它接受一個 ID,用于標識該時間塊。宏的作用是記錄代碼塊執行的開始時間,然后通過 __rdtsc
獲取當前的周期數。同樣的,創建另一個宏 END_TIMED_BLOCK
,它在代碼塊結束時再次調用 __rdtsc
獲取結束時間,然后計算兩次周期數的差值,這個差值即為代碼執行的周期數。
為了避免在同一作用域中多個時間塊可能造成的命名沖突,使用了 C++ 的拼接操作符,將 ID 添加到計數器名稱中。這樣可以確保每個計時器都有唯一的名稱,即使多個計時器位于同一函數內。
計時器的計數值會存儲在一個本地變量中,而實際的周期數則會存儲在相應的調試內存中。為了方便擴展,可以通過枚舉類型定義不同的計數器,如 DebugCycleCounter_Count
等。這樣可以使代碼更簡潔,同時也能避免硬編碼。
最終,計時器的使用方式變得非常簡單,只需要在代碼塊開始和結束時調用 BEGIN_TIMED_BLOCK(ID)
和 END_TIMED_BLOCK(ID)
,并指定對應的 ID,即可自動記錄并計算執行該塊代碼所消耗的處理器周期數。
編譯、清理并運行
在進行編譯之前,需要先解決一些錯誤。首先,調整類型定義,然后確保 time block
的值能夠正確累加到 CycleCount
中。還需要解引用該值以確保其正確更新。
修改完成后,理論上如果運行游戲,每次游戲代碼迭代時,周期計數器的值會逐步增加。接下來,考慮將代碼切換到發布模式進行編譯,了解為什么要這樣做,以便在最終版本中進行優化和性能驗證。
FillGroundChunk:關閉地面塊
為了減少構建地面塊時的運行時間壓力,暫時關閉了填充地面塊的功能。在這一過程中,地面塊仍然在生成,但它們被填充為黃色,而不是實際內容。這么做可以確保在調試計時器功能時,地面塊的構建不會對測試結果產生影響。等到計時器部分正常工作后,再重新啟用地面塊的填充功能。
查看計時器值
為了查看計時器的值,需要先確保在調用 game update and render
時,調試計數器中的所有計時器都被清零。可以使用一個類似 Windows Zero Memory
的方法來清除這些計時器。接著,創建一個新的函數來轉儲計時器信息,以便在每次更新和渲染后輸出這些計時器的值。
在 handleDebugCycleCounters
函數中,首先要確保只有在啟用了內部調試時,才執行相關代碼,避免在沒有調試計數器時出現編譯錯誤。然后,通過一個循環遍歷每個計數器,并使用 OutputDebugString
函數將每個計數器的值輸出。此輸出將包括計數器的索引及其相應的周期計數值。為了正確輸出 64 位整數,需要使用合適的格式符號。最終,這些輸出將幫助追蹤每個計時器的狀態并調試游戲性能。
這一過程為調試提供了基礎輸出,但將來可能會通過自定義調試服務進行更精細的處理。目前,這種方法雖然簡單,但足以滿足當前的需求。
運行游戲并查看調試循環計數
調試輸出現在可以顯示每幀游戲運行所消耗的周期數。從輸出中可以看到,當前每幀的周期數相對一致,但卻非常高。具體來說,當前游戲代碼本身就已經消耗了debug大約 31 億個周期,release 1.2億個周期 而不包括 Windows 操作系統的開銷。根據之前的計算,每個 CPU 核心每幀應當消耗的周期數應該低于 1.07 億,而現在的數字遠遠超出了這個預期。這表明,當前代碼的性能開銷較大,需要進一步優化。
debug模式 = 0: 3,289,537,500 三十多億個周期嗎
release模式 = 0: 119925458
確認我們知道的情況
從周期計數來看,可以確認目前的幀率遠低于期望值,游戲的性能無法達到理想的水平。周期計數顯示的數字遠遠超過了實際發布時所能接受的范圍,這證明了當前的代碼效率不高,無法支持足夠的幀率。因此,接下來的步驟是分析并改進代碼,優化性能以提高幀率。
RenderGroupToOutput:添加一個定時塊
為了更清楚地了解每個部分消耗的周期,可以通過在渲染和模擬等關鍵操作中添加計時器來跟蹤時間。例如,渲染的處理是通過 RenderGroupToOutput
函數完成的,可以在該函數內部插入一個時間塊,標記為 DebugCycleCounter_RenderGroupToOutput
。通過這種方式,能夠記錄并顯示渲染過程中花費的周期,從而進一步分析各個部分的性能瓶頸。
引入 DebugGlobalMemory 以便在不應訪問的情況下仍然能夠訪問這些計時信息
接下來,為了避免頻繁傳遞調試周期計數器,可以使用一個全局變量來存儲調試內存,從而讓所有地方都能訪問到它。這樣,雖然通常會避免使用全局變量,但在這種性能分析的情況下,它有助于簡化調試流程。這個全局變量 DebugGlobalMemory
會在內部模式下定義,并且只有在內部模式下編譯時才會有效,從而避免在發布版本中誤用。
此外,確保這些調試宏在其他平臺上完全被編譯掉,不會影響正常運行。通過在游戲更新和渲染過程中設置這個全局變量,可以保證每次訪問時都能獲得調試數據,而無需改變程序的架構。
注意兩個循環計數之間的差異
通過查看調試周期計數器的輸出,可以清楚地看到游戲的整體性能情況。通過對比不同部分的周期數,可以得出以下結論:整個游戲的運行只消耗了大約 267286次周期,而渲染部分則消耗了剩余的大部分周期,約111686539多次。這一數據確認了之前的觀察結果,即游戲本身的計算工作量較少,而渲染過程則是性能瓶頸的主要來源。
盡管這還不是非常精確的分析,但已經能大致勾畫出性能瓶頸的位置,驗證了游戲中明顯的性能下降正是由于渲染部分的高周期消耗所導致的。接下來,計劃繼續深入分析并優化這一部分。
0: 111953825 - 1: 111686539 = 267286
確認 DrawRectangleSlowly 是罪魁禍首
為了進一步確認性能瓶頸所在,決定對 DrawRectangleSlowly
這一函數進行檢查。雖然最初的假設是渲染部分可能導致性能問題,但在沒有確認之前,不能僅憑直覺做出優化,因為這可能會浪費時間在非關鍵部分。實際運行時,結果證明了這一假設,幾乎所有的時間都花費在了 DrawRectangleSlowly
函數上。
通過對比不同函數的周期消耗,發現大部分時間的消耗確實集中在渲染部分,尤其是在繪制小三角形的過程中。這也證明了渲染環節仍然是性能瓶頸的核心。雖然渲染過程中可能還有一些其他小的耗時操作,但問題的主要根源已經明確,為后續的優化提供了方向。
引入 HitCount 以了解 DrawRectangleSlowly 是因為本身慢,還是因為調用次數太多
為了進一步確認 rawRectangleSlowly
是否真的因為本身效率低導致性能問題,還是因為其調用頻率過高,決定在現有的周期計數系統中加入執行次數的統計。通過為每個函數路徑增加一個“命中計數”,可以追蹤每個代碼塊被執行的次數。
這樣,除了更新周期計數(DTSC
),還可以增加對命中計數的更新,記錄函數被調用的次數。為了展示這些數據,擴展了輸出,除了顯示周期計數外,還增加了每個函數調用的命中次數、周期數和每次調用的平均周期數(周期數除以命中次數)。這有助于確認是否是函數調用過于頻繁導致了性能瓶頸。對于命中計數為零的情況,避免除以零的錯誤,只在有命中次數時進行輸出,確保輸出的數據有效。
發現我們調用渲染器 13 次,DrawRectangleSlowly 被調用了 202 次
通過加入命中計數,發現了一個之前未曾注意到的現象:渲染器被調用了13次,原因可能是因為正在刷新地面塊。原本認為渲染器只會調用一次,但實際情況并非如此。另外,rawRectangleSlowly
被調用了202次。通過這些數據可以看出,問題并不是矩形繪制過多,而是這些矩形本身足夠大且繪制速度較慢,導致每個矩形的繪制消耗了大量時間。
這些結果確認了之前的假設,但更重要的是,這個過程展示了如何通過層級化的調試方法來確認性能瓶頸所在。在不知道時間消耗具體位置的情況下,這種方法非常有效,能夠幫助快速定位性能問題。
計算我們正在填充的像素數
為了進一步分析性能,決定臨時添加一個計數器來統計在渲染過程中實際填充了多少像素。通過在rawRectangleSlowly
函數中添加FillPixel
和TestPixel
計數器,可以跟蹤每次循環中測試和填充的像素數量。從結果來看,大部分測試的像素確實被填充了,表明并沒有浪費太多時間在無效的像素上,這是一個積極的信號。
然而,值得注意的是,雖然FillPixel
和TestPixel
的數量相似,但這也暴露出一個潛在的問題:如果沒有進行旋轉,理論上應該能精確計算出需要填充的像素。因此,若兩者沒有完全一致,可能表明邊界計算或邊緣函數的實現存在問題,這是一個明顯的警示信號,需要重新檢查邊界計算或修正邊緣函數。
解讀數據
正在討論填充的像素數量。首先,需要確認實際填充了多少像素,因此檢查了這個數字。然后,查看了一個名為“scratch buffer”的內容,這顯示了已經測試過的像素數量。接著,考慮到當前分辨率,想知道屏幕上實際上有多少像素。分辨率為1204 x 800,計算結果為963200。這一過程被重復進行了確認。
TestFill -> 1531114h
total -> 963200
注意我們操作的像素數并沒有比屏幕上的總像素數多多少
通過比較當前填充的像素數量與屏幕上總像素數,可以看出,實際上填充的像素并沒有超過總像素的太多,這意味著在過度繪制(overdraw)方面的表現還算不錯。過度繪制是指在渲染過程中,同一個像素被多次填充的情況,當前的情況表明并沒有出現過多的無效繪制。
黑板:過度繪制
過度繪制(overdraw)是指在渲染過程中,像素被多次觸及的情況。理想的渲染器應該只觸及每個像素一次,并且準確地給每個像素上色。理想情況下,渲染器的操作數應該等于屏幕上總像素的數量,這意味著每個像素僅被繪制一次。過度繪制衡量的是渲染器的效率,表示渲染器在渲染過程中需要重新繪制的像素次數。過度繪制的增加意味著渲染效率低下,因為每次覆蓋之前的像素都浪費了工作。因此,過度繪制與渲染效率密切相關。
目前的測試結果顯示,測試像素的數量與屏幕上的像素數量差距不大,這表明過度繪制的情況并不嚴重,渲染效率還算不錯。然而,當前屏幕上沒有太多內容,因此隨著內容的增加,過度繪制的數量可能會上升,需要通過創建一些測試場景來故意增加過度繪制,以便進一步優化這一指標。
黑板:進度報告
經過這些步驟,現在已經了解了一些關鍵的性能特點。首先,已經確定了性能瓶頸的具體位置,并且了解了一些關于該瓶頸的特征。目前,并沒有出現顯著的過度繪制問題,渲染效率還可以。然而,存在較大的速度問題。從周期數來看,每個像素的處理時間約為160個周期。通過將像素數量與所消耗的周期數相除,可以得出每個像素大約需要160個周期的處理時間。
關閉 NormalMap
目前,每個像素的處理時間大約是160個周期,這為進一步評估性能提供了一些信息。接下來,通過不進行法線貼圖合成的實驗,可以進一步了解渲染速度的變化。在沒有法線貼圖的情況下,渲染速度的差距相對較小,處理周期也有所減少。這表明法線貼圖合成對當前性能的影響不小。
理解大致的時間估算
目前的周期計數只是一個大概的估算,不能準確反映執行時間。即使游戲狀態沒有變化,渲染的內容也相同,周期數依然會波動。這是因為現代處理器復雜,存在內存訪問延遲、緩存命中、任務切換等因素,這些都會引入不確定性,導致每次周期計數不同。為了獲得更準確的周期計數,可以通過多次運行同一個操作,取最低周期數來減少這些變動,這樣可以排除外部干擾并盡量將操作保持在緩存中,從而更精確地測量最優周期數。然而,目前所看到的周期計數只能作為粗略的參考,不能視為絕對準確的數值。在查看分析結果時,必須理解這些數字的含義以及它們的準確性。
進行估算
為了估算每個像素所需的操作,首先需要分析渲染過程中的各種操作步驟。需要進行的操作包括變量重命名、減法、點積運算、取反、比較、屏幕空間坐標計算、乘法和采樣等。一些步驟(如旋轉、顏色空間轉換和分解)可能需要額外的乘法、加法、減法和位移操作,特別是在進行采樣時。對于內存查找部分,涉及的操作會更復雜,因此需要考慮到的指令總數大致為96條。
通過粗略估算,如果每個指令需要2個周期,那么每個像素可能需要200個周期。若能夠優化處理,使得每個周期可并行處理四個像素,那么每個像素的周期數將降到50個周期。假設通過優化將操作降到這種程度,填充當前屏幕的像素可能需要4200萬個周期。基于當前的測試像素數,如果能夠減少到100個周期以下,則可能達到預期的性能目標。
最終,優化的目標是盡量將每個像素的周期數降低到100周期以內,同時考慮到法線映射等額外開銷。這一過程需要在未來進一步細化和驗證,以確保渲染效率符合需求。
AVX-512 熱議
如果使用像AVX2或即將推出的AVX-512處理器,就可以顯著提高效率。相比每次處理4個像素,新的處理器能夠每次處理16個像素,這將大大提高渲染速度,帶來顯著的性能提升。因此,考慮到這種硬件進展,可能會在未來優化渲染性能時發揮巨大作用。
為什么計數器上沒有文本標簽?
在計數器上沒有文本標簽的原因是,目前并不需要它們。雖然可以考慮以后加上,但目前并不覺得有必要。
對不起,如果這是你之前講過的內容,但能否解釋一下 C++ 中 new 和 malloc() 之間的區別,以及何時使用它們?
new
和 malloc
之間的區別在于是否需要使用 C++ 的特性。malloc
僅分配內存,而 new
不僅分配內存,還會調用對象的構造函數。因此,使用帶有構造函數的對象時,必須使用 new
,除非打算手動調用構造函數。對于沒有構造函數的對象,new
并不會做任何額外的事情,malloc
就足夠了。類似地,對于帶有析構函數的對象,需要使用 delete
來釋放內存,而對于沒有析構函數的對象,可以直接使用 free
來釋放內存。
你是否可以通過做更多的工作來省略一些指令,例如 d - XAxis,接著 d - XAxis - YAxis。那應該只需要 2 條指令嗎?
通過對之前的計算結果進行復用來減少指令的數量是完全可行的。例如,可以通過先計算 D - XAxis
,然后再計算 d - XAxis - YAxis
來節省一些計算。雖然編譯器已經開啟優化,可能會自動去除一些冗余的操作,但依然會盡量手動進行優化,避免多余的計算。
你認為我們會在軟件渲染器中使用多線程嗎?
在軟件渲染中,確實會考慮使用多線程。計劃將屏幕分成四個部分,并在不同的線程中分別渲染每一部分。這種方式能夠提高渲染效率,并利用多核處理器的優勢。
是否可以每次操作都做四倍優化?
處理器如何發布指令以及如何管理不同指令的執行。處理器的工作方式是,它只能在有足夠空閑單元的情況下發布指令。可以將處理器看作由多個較小的單元組成,每個單元負責不同的任務,例如加法或位移操作。如果兩個指令是獨立的,可以在同一周期內分別發給不同的單元執行。如果兩個指令相同且依賴于相同的資源(如同為位移指令),則無法在同一周期執行。
優化指令發布的過程涉及深入了解處理器的結構,例如每個單元的數量和處理器是否能夠在一個周期內同時執行多個指令。這個過程是復雜的,需要了解處理器的每個細節,并考慮它的亂序執行窗口。對于x64處理器來說,由于其復雜性和多樣性,這類優化特別困難。盡管如此,在某些情況下,確實有必要深入了解處理器,以實現最大化的性能優化。
我的意思是,把它放入寬指令(SIMD)中
關于是否可以在指令中進行四倍(quad pump)操作的問題,回答是否定的。雖然浮點和整數運算通常可以進行四倍泵,但內存訪問操作(例如紋理獲取和計算需要加載的紋素位置)是無法進行的。這是因為SSE2、AVX及其之前的指令集在內存訪問方面有局限,無法處理寬操作(wide operations)。雖然AVX-512可能解決了這個問題,因為Larrabee指令集就包含了這些功能,并且AVX-512已經將這些功能整合到主流指令集中了,但在目前的指令集下,內存訪問仍然是一個瓶頸。
“Quad pump” 這個術語通常指的是在一個時鐘周期內并行執行四個操作或指令的能力。它是在處理器架構中,特別是與SIMD(單指令多數據)指令集相關的一個術語,比如AVX(高級向量擴展)系列。
具體來說,“quad pump” 的意思是在一次時鐘周期內,通過使用寬度較大的指令集(例如 AVX-512),能夠并行處理更多的數據,通常是每個時鐘周期處理四倍于常規操作的數據量。
例如,在一個支持 AVX 的處理器上,如果能夠處理四個浮點數或者四個整數數據項,就可以稱其為“quad pumping”。這使得處理器在執行某些任務時,能夠更高效地同時處理多個數據項,顯著提高性能,尤其在圖形處理、科學計算等需要大量并行運算的領域。
linux 下面perf
在Linux中執行CPU profiling通常可以使用多種工具,以下是幾種常見的方法:
performance_test.cpp
#include <iostream>
#include <vector>
#include <cmath>
#include <chrono>// 模擬一個計算密集型函數
void compute_heavy_task(int size) {std::vector<double> vec(size, 0.0);for (int i = 0; i < size; ++i) {for (int j = 0; j < size; ++j) {vec[i] += sqrt(i * j);}}
}int main() {int size = 1000; // 調整為較大的數值,增加計算密集性// 記錄開始時間auto start = std::chrono::high_resolution_clock::now();// 執行計算密集型任務compute_heavy_task(size);// 記錄結束時間并計算花費的時間auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> duration = end - start;std::cout << "Task completed in " << duration.count() << " seconds." << std::endl;return 0;
}
1. 使用 perf
工具
perf
是一個強大的性能分析工具,可以用于收集CPU性能數據,檢查程序瓶頸。
安裝 perf
(如果尚未安裝):
sudo apt-get install linux-tools-common linux-tools-generic
基本使用:
- 首先,編譯程序并啟用調試信息:
g++ -g -o performance_test performance_test.cpp
- 然后,使用
perf
來記錄程序性能:
perf record ./performance_test
- 程序運行完后,會生成一個
perf.data
文件。可以使用以下命令查看性能報告:
perf report
采樣特定的事件:
你可以通過 perf
來捕獲特定的事件,比如 CPU cycles 或緩存命中等:
perf record -e cycles -p <pid>
這會捕獲指定進程(PID)的 CPU 周期事件。
rm 刪掉除.cpp 的其他文件
要刪除當前目錄下除了 .cpp
文件之外的其他文件,可以使用 find
命令結合 rm
來實現。以下是一個示例:
find . -type f ! -name "*.cpp" -exec rm -f {} \;
解釋:
find .
:從當前目錄開始查找。-type f
:只查找文件。! -name "*.cpp"
:排除.cpp
文件,只刪除不以.cpp
結尾的文件。-exec rm -f {} \;
:對找到的每個文件執行rm -f
命令,刪除文件。
注意:
- 確保你在正確的目錄中執行命令,避免刪除了不該刪除的文件。
- 如果不確定,可以先使用
find
命令列出將被刪除的文件,例如:
這將列出所有不以find . -type f ! -name "*.cpp"
.cpp
結尾的文件,你可以確認后再執行刪除操作。
2. 使用 gprof
工具
gprof
是一個較為傳統的性能分析工具,可以幫助分析程序的執行性能。
使用步驟:
-
編譯程序 時,確保加上
-pg
選項來啟用性能分析:g++ -pg -o performance_test performance_test.cpp
-
執行程序,生成性能數據:
./performance_test
-
生成
gmon.out
文件后,使用gprof
查看性能報告:gprof ./performance_test gmon.out > analysis.txt
3. 使用 valgrind
(callgrind 模式)
valgrind
是一個用于內存調試的工具,但其 callgrind
模式可以用于進行性能分析,尤其適用于 CPU 性能分析。
安裝 valgrind
:
sudo apt-get install valgrind
使用 callgrind
:
valgrind --tool=callgrind ./performance_test
這會生成一個包含函數調用次數、調用圖等信息的文件,可以使用 kcachegrind
或 qcachegrind
來可視化分析。
修改WSL 界面的字體大小
1. 安裝字體
sudo apt update
sudo apt install fontconfig
2. 安裝 x11-xserver-utils
包
xrdb
工具通常包含在 x11-xserver-utils
包中。在 WSL 中執行以下命令來安裝它:
sudo apt update
sudo apt install x11-xserver-utils
安裝完成后,您應該能夠使用 xrdb
命令。
3. 接下來,創建一個名為.Xresources的文件并在其中添加以下內容:
Xft.dpi: 220
這將把Xft的dpi設置為220,從而放大字體大小。
3. 再次嘗試執行 xrdb
安裝完 x11-xserver-utils
后,您可以重新嘗試運行 xrdb
:
xrdb -merge ~/.Xresources
如果沒有錯誤提示,并且沒有顯示任何輸出,說明 X 資源文件已經成功合并。
總結
- 如果
xrdb
找不到,安裝x11-xserver-utils
包。 - 確保配置的
~/.Xresources
文件格式正確。 - 使用
xrdb -merge ~/.Xresources
來合并配置文件。
valgrind doc: https://valgrind.org/docs/manual/index.html