主頁 > 作業系統 > 2022深讀《嵌入式Linux記憶體使用與性能優化》筆記

2022深讀《嵌入式Linux記憶體使用與性能優化》筆記

2022-04-29 06:09:06 作業系統

尊重原創著作權: https://www.gewuweb.com/hot/10176.html

《嵌入式Linux記憶體使用與性能優化》筆記

尊重原創著作權: https://www.gewuweb.com/sitemap.html

《嵌入式Linux記憶體使用與性能優化》筆記

這本書有兩個關切點:系統記憶體(用戶層)和性能優化,

這本書和Brendan Gregg的《Systems
Performance》相比,無論是技術層次還是更高的理論都有較大差距,但是這不影響,快速花點時間簡單過一遍,

然后在對《Systems Performance》進行詳細的學習,

由于Ubuntu測驗驗證更合適,所以在Ubuntu(16.04)+Kernel(4.10.0)環境下做了下面的實驗,

全書共9章:14章著重于記憶體的使用,盡量降低行程的記憶體使用量,定位和發現記憶體泄露;59章著重于如何讓系統性能優化,提高執行速度,

第1章 記憶體的測量

第2章 行程記憶體優化

第3章 系統記憶體優化

第4章 記憶體泄露

第5章 性能優化的流程

第6章 行程啟動速度

第7章 性能優化的方法

第8章 代碼優化的境界

第9章 系統性能優化


用戶空間的記憶體使用量是由行程使用量累積和系統使用之和,所以優化系統記憶體使用,就是逐個攻克每個行程的使用量和優化系統記憶體使用,,

俗話說“知己知彼,百戰不殆”,要優化一個行程的使用量,首先得使用工具去評估記憶體使用量( 第1章 記憶體的測量 );

然后就來看看行程那些部分耗費記憶體,并針對性進行優化( 第2章 行程記憶體優化 );

最后從系統層面尋找方法進行優化( 第3章 系統記憶體優化 ),

記憶體的使用一個致命點就是記憶體泄露,如何發現記憶體泄露,并且將記憶體泄露定位是重點( 第4章 記憶體泄露 )

第1章 記憶體的測量

關于系統記憶體使用,將按照 (1)明確目標 -> (2) 尋找評估方法, (3)了解當前狀況->對系統記憶體進行優化->重新測量
,評估改善狀況的程序,來闡述系統的記憶體使用與優化,

(1)明確目標,針對系統記憶體優化,有兩個:

A. 每個守護行程使用的記憶體盡可能少

B. 長時間運行后,守護行程記憶體仍然保持較低使用量,沒有記憶體泄露 ,

(2)尋找評估方法,第1章關注點,

(3)對系統記憶體進行優化,第2章針對行程進行優化,第3章針對系統層面進行記憶體優化,第4章關注記憶體泄露,

系統記憶體測量

free用以獲得當前系統記憶體使用情況,

在一嵌入式設備獲取如下:

busybox free
total used free shared buffers
Mem: 23940 15584 8356 0 0 (23940=15584+8356)
-/+ buffers: 15584 8356
Swap: 0 0 0


和PC使用的free對比:

total used free shared buffers cached
Mem: 14190636 10494128 3696508 587948 1906824 5608888
-/+ buffers/cache: 2978416 11212220
Swap: 7999484 68844 7930640


可見這兩個命令存在差異,busybox沒有cached,這和實際不符,實際可用記憶體=free+buffers+cached,

buffers是用來給Linux系統中塊設備做緩沖區,cached用來緩沖打開的檔案,下面是通過cat
/proc/meminfo獲取,可知實際可用記憶體=8352+0+3508=11860,已經使用記憶體為=23940-11860=12080,可見兩者存在差異,
busybox的free不太準確;/proc/meminfo的資料更準確,

MemTotal: 23940 kB
MemFree: 8352 kB
Buffers: 0 kB
Cached: 3508 kB


行程記憶體測量

在行程的proc中與記憶體有關的節點有statm、maps、memmap,

cat /proc/xxx/statm

1086 168 148 1 0 83 0


這些引數以頁(4K)為單位,分別是:

1086 Size,任務虛擬地址空間的大小,

168 Resident,應用程式正在使用的物理記憶體的大小,

148 Shared,共享頁數,

1 Trs,程式所擁有的可執行虛擬記憶體的大小,

0 Lrs,被映像到任務的虛擬記憶體空間的的庫的大小,

83 Drs,程式資料段和用戶態的堆疊的大小,

0 dt,臟頁數量(已經修改的物理頁面),

Size、Trs、Lrs、Drs對應虛擬記憶體,Resident、Shared、dt對應物理記憶體,

cat /proc/xxx/maps

00400000-00401000 r-xp 00000000 08:05 18561374 /home/lubaoquan/temp/hello
00600000-00601000 r--p 00000000 08:05 18561374 /home/lubaoquan/temp/hello
00601000-00602000 rw-p 00001000 08:05 18561374 /home/lubaoquan/temp/hello
00673000-00694000 rw-p 00000000 00:00 0 [heap]
7f038c1a1000-7f038c35f000 r-xp 00000000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f038c35f000-7f038c55e000 ---p 001be000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f038c55e000-7f038c562000 r--p 001bd000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f038c562000-7f038c564000 rw-p 001c1000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f038c564000-7f038c569000 rw-p 00000000 00:00 0
7f038c569000-7f038c58c000 r-xp 00000000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f038c762000-7f038c765000 rw-p 00000000 00:00 0
7f038c788000-7f038c78b000 rw-p 00000000 00:00 0
7f038c78b000-7f038c78c000 r--p 00022000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f038c78c000-7f038c78d000 rw-p 00023000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f038c78d000-7f038c78e000 rw-p 00000000 00:00 0
7ffefe189000-7ffefe1aa000 rw-p 00000000 00:00 0 [stack]
7ffefe1c4000-7ffefe1c6000 r--p 00000000 00:00 0 [vvar]
7ffefe1c6000-7ffefe1c8000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]


第一列,代表該記憶體段的虛擬地址,

第二列,r-xp,代表該段記憶體的權限,r=讀,w=寫,x=執行,s=共享,p=私有,

第三列,代表在行程地址里的偏移量,

第四列,映射檔案的的主從設備號,

第五列,映像檔案的節點號,

第六列,映像檔案的路徑,

《嵌入式Linux記憶體使用與性能優化》筆記

kswapd

Linux存在一個守護行程kswapd,他是Linux記憶體回識訓制,會定期監察系統中空閑呢村的數量,一旦發現空閑記憶體數量小于一個閾值的時候,就會將若干頁面換出,

但是在嵌入式Linux系統中,卻沒有交換磁區,沒有交換磁區的原因是:

1.一旦使用了交換磁區,系統系能將下降得很快,不可接受,

2.Flash的寫次數是有限的,如果在Flash上面建立交換磁區,必然導致對Flash的頻繁讀寫,影響Flash壽命,

那沒有交換磁區,Linux是如何做記憶體回收的呢?

對于那些沒有被改寫的頁面,這塊記憶體不需要寫到交換磁區上,可以直接回收,

對于已經改寫了的頁面,只能保留在系統中,,沒有交換磁區,不能寫到Flash上,

在Linux物理記憶體中,每個頁面有一個dirty標志,如果被改寫了,稱之為dirty page,所有非dirty page都可以被回收,


第2章 行程記憶體優化

當存在很多守護行程,又要去降低守護行程記憶體占用量,如何去推動:

1.所有守護行程記憶體只能比上一個版本變少,

2.Dirty Page排前10的守護行程,努力去優化,dirty page減少20%,

可以從三個方面去優化:

1.執行檔案所占用的記憶體

2.動態庫對記憶體的影響

3.執行緒對記憶體的影響

2.1 執行檔案

一個程式包括代碼段、資料段、堆段和堆疊段,一個行程運行時,所占用的記憶體,可以分為如下幾部分:

堆疊區(stack):由編譯器自動分配釋放,存放函式的引數、區域變數等

堆區(heap):一般由程式員分配釋放,若程式員不釋放,程式結束時可有作業系統來回收

全域變數、靜態變數:初始化的全域變數和靜態變數在一塊區域,未初始化的全域變數和靜態變數在另一塊區域,程式結束后由系統釋放

文字常量:常量、字串就是放在這里的,程式結束后有系統釋放

程式代碼:存放函式體的二進制代碼

下面結合一個實體分析:

include <stdlib.h>

include <stdio.h>

int n=10;
const int n1=20;
int m;

int main()
{
int s=7;
static int s1=30;
char *p=(char *)malloc(20);
pid_t pid=getpid();

printf("pid:%d\n", pid);
printf("global variable address=%p\n", &n);
printf("const global address=%p\n", &n1);
printf("global uninitialization variable address=%p\n", &m);;
printf("static variable address=%p\n", &s1);
printf("stack variable address=%p\n", &s);
printf("heap variable address=%p\n", &p);
pause();
}


執行程式結果:

pid:18768
global variable address=0x601058
const global address=0x400768
global uninitialization variable address=0x601064
static variable address=0x60105c
stack variable address=0x7ffe1ff7d0e0
heap variable address=0x7ffe1ff7d0e8


查看cat /proc/17868/maps

00400000-00401000 r-xp 00000000 08:05 18561376 /home/lubaoquan/temp/example
(只讀全域變數n1位于行程的代碼段)
00600000-00601000 r--p 00000000 08:05 18561376 /home/lubaoquan/temp/example
00601000-00602000 rw-p 00001000 08:05 18561376 /home/lubaoquan/temp/example
(全域初始變數n、全域未初始變數m、區域靜態變數s1,都位于行程的資料段)
00771000-00792000 rw-p 00000000 00:00 0 [heap]
7f7fb86a2000-7f7fb8860000 r-xp 00000000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f7fb8860000-7f7fb8a5f000 ---p 001be000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f7fb8a5f000-7f7fb8a63000 r--p 001bd000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f7fb8a63000-7f7fb8a65000 rw-p 001c1000 08:01 3682126 /lib/x86_64-linux-
gnu/libc-2.19.so
7f7fb8a65000-7f7fb8a6a000 rw-p 00000000 00:00 0
7f7fb8a6a000-7f7fb8a8d000 r-xp 00000000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f7fb8c63000-7f7fb8c66000 rw-p 00000000 00:00 0
7f7fb8c89000-7f7fb8c8c000 rw-p 00000000 00:00 0
7f7fb8c8c000-7f7fb8c8d000 r--p 00022000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f7fb8c8d000-7f7fb8c8e000 rw-p 00023000 08:01 3682489 /lib/x86_64-linux-
gnu/ld-2.19.so
7f7fb8c8e000-7f7fb8c8f000 rw-p 00000000 00:00 0
7ffe1ff5f000-7ffe1ff80000 rw-p 00000000 00:00 0 [stack]
(區域變數s、malloc分配記憶體指標p都位于堆疊段)
7ffe1ffbb000-7ffe1ffbd000 r--p 00000000 00:00 0 [vvar]
7ffe1ffbd000-7ffe1ffbf000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]



第3章 系統記憶體優化

3.1 守護行程的記憶體使用

守護行程由于上期運行,對系統記憶體使用影響很大:

1.由于一直存貨,所以其占用的記憶體不會被釋放,

2.即使什么都不做,由于參考動態庫,也會占用大量的物理記憶體,

3.由于生存周期很長,哪怕一點記憶體泄露,累積下來也會很大,導致記憶體耗盡,

那么如何降低風險呢?

1.設計守護行程時,區分常駐部分和非常駐部分,盡量降低守護行程的邏輯,降低記憶體占用,降低記憶體泄露幾率,或者將幾個守護行程內容合為一個,

2.有些行程只是需要盡早啟動,而不需要變成守護行程,可以考慮加快啟動速度,從而使服務達到按需啟動的需求,優化方式有優化加載動態庫、使用Prelink方法、采用一些行程調度方法等,

3.2 tmpfs磁區

Linux中為了加快檔案讀寫,基于記憶體建立了一個檔案系統,成為ramdisk或者tmpfs,檔案訪問都是基于物理記憶體的,

使用df -k /tmp可以查看磁區所占空間大小:

Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 77689292 9869612 63850172 14% /


在對這個磁區進行讀寫時,要時刻注意,他是占用物理記憶體的,不需要的檔案要及時洗掉,

3.3 Cache和Buffer

系統空閑記憶體=MemFree+Buffers+Cached,

Cache也稱快取,是把從Flash中讀取的資料保存起來,若再次讀取就不需要去讀Flash了,直接從快取中讀取,從而提高讀取檔案速度,Cache快取的資料會根據讀取頻率進行組織,并最頻繁讀取的內容放在最容易找到的位置,把不再讀的內容不短往后排,直至從中洗掉,
在程式執行程序中,發現某些指令不在記憶體中,便會產生page fault,將代碼載入到物理記憶體,程式退出后,代碼段記憶體不會立即丟棄,二是作為Cache快取,

Buffer也稱快取,是根據Flash讀寫設計的,把零散的寫操作集中進行,減少Flash寫的次數,從而提高系統性能,

Cache和BUffer區別簡單的說都是RAM中的資料, Buffer是即將寫入磁盤的,而Cache是從磁盤中讀取的 ,

使用free -m按M來顯示Cache和Buffer大小:

total used free shared buffers cached
Mem: 13858 1204 12653 206 10 397
-/+ buffers/cache: 796 13061
Swap: 7811 0 7811


降低Cache和Buffer的方法:

sync
該命令將未寫的系統緩沖區寫到磁盤中,包含已修改的 i-node、已延遲的塊 I/O 和讀寫映射檔案,

/proc/sys/vm/drop_caches
a)清理pagecache(頁面快取)

echo 1 > /proc/sys/vm/drop_caches 或者 # sysctl -w vm.drop_caches=1

b)清理dentries(目錄快取)和inodes

echo 2 > /proc/sys/vm/drop_caches 或者 # sysctl -w vm.drop_caches=2

c)清理pagecache、dentries和inodes

echo 3 > /proc/sys/vm/drop_caches 或者 # sysctl -w vm.drop_caches=3

上面三種方式都是臨時釋放快取的方法,要想永久釋放快取,需要在/etc/sysctl.conf檔案中配置:vm.drop_caches=1/2/3,然后sysctl
-p生效即可!

vfs_cache_pressure
vfs_cache_pressure=100
這個是默認值,內核會嘗試重新宣告dentries和inodes,并采用一種相對于頁面快取和交換快取比較”合理”的比例,
減少vfs_cache_pressure的值,會導致內核傾向于保留dentry和inode快取,
增加vfs_cache_pressure的值,(即超過100時),則會導致內核傾向于重新宣告dentries和inodes
總之,vfs_cache_pressure的值:
小于100的值不會導致快取的大量減少
超過100的值則會告訴內核你希望以高優先級來清理快取,

3.4 記憶體回收

kswapd有兩個閾值:pages_high和pages_low,當空閑記憶體數量低于pages_low時,kswapd行程就會掃描記憶體并且每次釋放出32個free
pages,知道free page數量達到pages_high,

kswapd回收記憶體的原則?

1.如果物理頁面不是dirty page,就將該物理頁面回收,

代碼段,只讀不能被改寫,所占記憶體都不是dirty page,資料段,可讀寫,所占記憶體可能是dirty
page,也可能不是,堆段,沒有對應的映射檔案,內容都是通過修改程式改寫的,所占物理記憶體都是dirty
page,堆疊段和堆段一樣,所占物理記憶體都是dirty page,共享記憶體,所占物理記憶體都是dirty page,

就是說,這條規則主要面向 行程的代碼段和未修改的資料段 ,

2.如果物理頁面已經修改并且可以備份回檔案系統,就呼叫pdflush將記憶體中的內容和檔案系統進行同步,pdflush寫回磁盤,主要針對Buffers,

3.如果物理頁面已經修改但是沒有任何磁盤的備份,就將其寫入swap磁區,

kswapd再回首程序中還存在兩個重要方法:LMR(Low on Memory Reclaiming)和OMK(Out of Memory Killer),

由于kswapd不能提供足夠空閑記憶體是,LMR將會起作用,每次釋放1024個垃圾頁知道記憶體分配成功,

當LMR不能快速釋放記憶體的時候,OMK就開始起作用,OMK會采用一個選擇演算法來決定殺死某些行程,發送SIGKILL,就會立即釋放記憶體,

3.5 /proc/sys/vm優化

此檔案夾下面有很多介面控制記憶體操作行為,在進行系統級記憶體優化的時候需要仔細研究,適當調整,

block_dump
表示是否打開Block Debug模式,用于記錄所有的讀寫及Dirty Block寫回操作,0,表示禁用Block
Debug模式;1,表示開啟Block Debug模式,

dirty_background_ratio
表示臟資料達到系統整體記憶體的百分比,此時觸發pdflush行程把臟資料寫回磁盤,

dirty_expires_centisecs
表示臟資料在記憶體中駐留時間超過該值,pdflush行程在下一次將把這些資料寫回磁盤,預設值3000,單位是1/100s,

dirty_ratio
表示如果行程產生的臟資料達到系統整體記憶體的百分比,此時行程自行吧臟資料寫回磁盤,

dirty_writeback_centisecs
表示pdflush行程周期性間隔多久把臟資料協會磁盤,單位是1/100s,

vfs_cache_pressure
表示內核回收用于directory和inode
cache記憶體的傾向;預設值100表示內核將根據pagecache和swapcache,把directory和inode
cache報紙在一個合理的百分比;降低該值低于100,將導致內核傾向于保留directory和inode
cache;高于100,將導致內核傾向于回收directory和inode cache,

min_free_kbytes
表示強制Linux VM最低保留多少空閑記憶體(KB),

nr_pdflush_threads
表示當前正在進行的pdflush行程數量,在I/O負載高的情況下,內核會自動增加更多的pdflush,

overcommit_memory
指定了內核針對記憶體分配的策略,可以是0、1、2.
0 表示內核將檢查是否有足夠的可用記憶體供應用行程使用,如果足夠,記憶體申請允許;反之,記憶體申請失敗,
1 表示內核允許分配所有物理記憶體,而不管當前記憶體狀態如何,
2 表示內核允許分配查過所有物理記憶體和交換空間總和的記憶體,

overcommit_ratio
如果overcommit_memory=2,可以過在記憶體的百分比,

page-cluster
表示在寫一次到swap區時寫入的頁面數量,0表示1頁,3表示8頁,

swapiness
表示系統進行交換行為的成都,數值(0~100)越高,越可能發生磁盤交換,

legacy_va_layout
表示是否使用最新的32位共享記憶體mmap()系統呼叫,

nr_hugepages
表示系統保留的hugetlg頁數,


第4章 記憶體泄露

4.1 如何確定是否有記憶體泄露

解決記憶體泄露一個好方法就是:不要讓你的行程成為一個守護行程,完成作業后立刻退出,Linux會自動回收該行程所占有的記憶體,
測驗記憶體泄露的兩種方法:

1.模仿用戶長時間使用設備,查看記憶體使用情況,對于那些記憶體大量增長的行程,可以初步懷疑其有記憶體泄露,

2.針對某個具體測驗用例,檢查是否有記憶體泄露,

在發現行程有漏洞之后,看看如何在代碼中檢查記憶體泄露,

4.2 mtrace

glibc針對記憶體泄露給出一個鉤子函式mtrace:

1.加入頭檔案<mcheck.h>

2.在需要記憶體泄露檢查的代碼開始呼叫void mtrace(),在需要記憶體泄露檢查代碼結尾呼叫void
muntrace(),如果不呼叫muntrace,程式自然結束后也會顯示記憶體泄露

3.用debug模式編譯檢查代碼(-g或-ggdb)

4.在運行程式前,先設定環境變數MALLOC_TRACE為一個檔案名,這一檔案將存有記憶體分配資訊

5.運行程式,記憶體分配的log將輸出到MALLOC_TRACE所執行的檔案中,

代碼如下:

include <stdio.h>

include <stdlib.h>

include <mcheck.h>

int main(void)
{
mtrace();

char *p=malloc(10);
return 0;
}


編譯,設定環境變數,執行,查看log:

gcc -o mem-leakage -g mem-leakage.c

export MALLOC_TRACE=/home/lubaoquan/temp/malloc.og

./mem-leakage

= Start
@ ./mem-leakage:[0x400594] + 0x100d460 0xa (0xa表示泄露的記憶體大小,和malloc(10)對應)


加入mtrace會導致程式運行緩慢:

1.日志需要寫到Flash上(可以將MALLOC_TRACE顯示到stdout上,)

2.mtrace函式內,試圖根據呼叫malloc代碼指標,決議出對應的函式


性能優化是一個艱苦、持續、枯燥、反復的程序,涉及到的內容非常多,編譯器優化、硬體體系結構、軟體的各種技巧等等,

另外在嵌入式電池供電系統上,性能的優化也要考慮到功耗的使能,PnP的兩個P(Power and Performance)是不可分割的部分,

第5章 性能優化的流程

5.1 性能評價

首先“快”與“慢”需要一個客觀的指標,同時明確定義測驗階段的起訖點,

同時優化也要考慮到可移植性以及普適性,不要因為優化過度導致其他問題的出現,

5.2 性能優化的流程

1. 測量,獲得資料,知道和目標性能指標的差距,

2. 分析待優化的程式,查找性能瓶頸,

3. 修改程式,

4. 重新測驗,驗證優化結果,

5. 達到性能要求,停止優化,不達目標,繼續分析,

《嵌入式Linux記憶體使用與性能優化》筆記

5.3 性能評測

介紹兩種方法:可視操作(攝像頭)和日志,

話說攝像頭錄像評測,還是很奇葩的,適用范圍很窄,但是貌似還是有一定市場,

5.4 性能分析

導致性能低下的三種主要原因:

(1) 程式運算量很大,消耗過多CPU指令,

(2) 程式需要大量I/O,讀寫檔案、記憶體操作等,CPU更多處于I/O等待,

(3) 程式之間相互等待,結果CPU利用率很低,

簡單來說即是CPU利用率高、I/O等待時間長、死鎖情況,

下面重點放在第一種情況,提供三種方法,

1. 系統相關:/proc/stat、/proc/loadavg

** cat /proc/stat ** 結果如下:

cpu  12311503 48889 7259266 561072284 575332 0 72910 0 0 0-----分別是user、nice、system、idle、iowait、irq、softirq、steal、guest、guest_nice
  user:從系統啟動開始累計到當前時刻,用戶態CPU時間,不包含nice值為負的行程,
  nice:從系統啟動開始累計到當前時刻,nice值為負的行程所占用的CPU時間,
  system:從系統啟動開始累計到當前時刻,內核所占用的CPU時間,
  idle:從系統啟動開始累計到當前時刻,除硬碟IO等待時間以外其他等待時間,
  iowait:從系統啟動開始累計到當前時刻,硬碟IO等待時間,
  irq:從系統啟動開始累計到當前時刻,硬中斷時間,
  softirq:從系統啟動開始累計到當前時刻,軟中斷時間,
  steal:從系統啟動開始累計到當前時刻,involuntary wait
  guest:running as a normal guest
  guest_nice:running as a niced guest


cpu0 3046879 11947 1729621 211387242 95062 0 1035 0 0 0 cpu1 3132086 8784 1788117 116767388 60010 0 535 0 0 0 cpu2 3240058 12964 1826822 116269699 353944 0 31989 0 0 0 cpu3 2892479 15192 1914705 116647954 66316 0 39349 0 0 0 intr 481552135 16 183 0 0 0 0 0 0 175524 37 0 0 2488 0 0 0 249 23 0 0 0 0 0 301 0 0 3499749 21 1470158 156 33589268 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-------------------Counts of interrupts services since boot time.Fist column is the total of all interrupts services, each subsequent if total for particular interrupt.
ctxt 2345712926-------------------------------------------------Toal number of context switches performed since bootup accross all CPUs.
btime 1510217813------------------------------------------------Give the time at which the system booted, in seconds since the Unix epoch.
processes 556059------------------------------------------------Number of processes and threads created, include(but not limited to) those created by fork() or clone() system calls.
procs_running 2-------------------------------------------------Current number of runnable threads
procs_blocked 1-------------------------------------------------Current number of threads blocked, waiting for IO to complete.
softirq 415893440 117 134668573 4001105 57050104 3510728 18 1313611 104047789 0 111301395---總softirq和各種型別softirq產生的中斷數:HI_SOFTIRQ,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

由cpu的各種時間可以推匯出:

CPU時間=user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice

CPU利用率=1-idle/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

CPU用戶態利用率=(user+nice)/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

CPU內核利用率=system/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

IO利用率=iowait/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

** cat /proc/loadavg ** 結果如下:

0.46 0.25 0.16 2/658 13300

1、5、15分鐘平均負載;

2/658:在采樣時刻,運行佇列任務數目和系統中活躍任務數目,

13300:最大pid值,包括執行緒,

2. 行程相關:/proc/xxx/stat

24021 (atop) S 1 24020 24020 0 -1 4194560 6179 53 0 0 164 196 0 0 0 -20 1 0 209898810 19374080 1630 18446744073709551615 1 1 0 0 0 0 0 0 27137 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0

3. top

top是最常用來監控系統范圍內行程活動的工具,提供運行在系統上的與CPU關系最密切的行程串列,以及很多統計值,


第6章 行程啟動速度

行程啟動可以分為兩部分:

(1) 行程啟動,加載動態庫,直到main函式值錢,這是還沒有執行到程式員撰寫的代碼,其性能優化有其特殊方法,

(2) main函式之后,直到對用戶的操作有所回應,涉及到自身撰寫代碼的優化,在7、8章介紹,

6.1 查看行程的啟動程序

hello原始碼如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("Hello world!\n");
  return 0;
}

編譯:

gcc -o hello -O2 hello.c

strace用于查看系統運行程序中系統呼叫,同時得知行程在加載動態庫時的大概程序,-tt可以列印微妙級別時間戳,

strace -tt ./hello如下:

20:15:10.185596 execve("./hello", ["./hello"], [/* 82 vars */]) = 0
20:15:10.186087 brk(NULL)               = 0x19ad000
20:15:10.186206 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
20:15:10.186358 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710ea000
20:15:10.186462 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
20:15:10.186572 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
20:15:10.186696 fstat(3, {st_mode=S_IFREG|0644, st_size=121947, ...}) = 0
20:15:10.186782 mmap(NULL, 121947, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f24710cc000
20:15:10.186857 close(3)                = 0
20:15:10.186975 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
20:15:10.187074 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
20:15:10.187153 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832----------------libc.so.6檔案句柄3,大小832,
20:15:10.187270 fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
20:15:10.187358 mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2470afd000
20:15:10.187435 mprotect(0x7f2470cbd000, 2097152, PROT_NONE) = 0
20:15:10.187558 mmap(0x7f2470ebd000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f2470ebd000---引數依次是:addr、length、prot、flags、fd、offset,
20:15:10.187662 mmap(0x7f2470ec3000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2470ec3000
20:15:10.187749 close(3)                = 0
20:15:10.187887 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710cb000
20:15:10.187992 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710ca000
20:15:10.188072 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710c9000
20:15:10.188191 arch_prctl(ARCH_SET_FS, 0x7f24710ca700) = 0--------------------------------set architecture-specific thread state, the parameters are code and addr,
20:15:10.188334 mprotect(0x7f2470ebd000, 16384, PROT_READ) = 0
20:15:10.188419 mprotect(0x600000, 4096, PROT_READ) = 0
20:15:10.188541 mprotect(0x7f24710ec000, 4096, PROT_READ) = 0
20:15:10.188633 munmap(0x7f24710cc000, 121947) = 0
20:15:10.188785 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
20:15:10.188965 brk(NULL)               = 0x19ad000
20:15:10.189158 brk(0x19ce000)          = 0x19ce000
20:15:10.189243 write(1, "Hello world!\n", 13Hello world!-----------------------------------往句柄1寫13個字符Hello world!\n,
) = 13
20:15:10.189299 exit_group(0)           = ?
20:15:10.189387 +++ exited with 0 +++

通過設定LD_DEBUG環境變數,可以列印出在行程加載程序中都做了那些事情:

LD_DEBUG=all ./hello如下,看似簡單的一個Hello world!,其系統已經做了很多準備作業,

     13755:    
     13755:    file=libc.so.6 [0];  needed by ./hello [0]----------(1) 搜索其所依賴的動態庫,
     13755:    find library=libc.so.6 [0]; searching
     13755:     search cache=/etc/ld.so.cache
     13755:      trying file=/lib/x86_64-linux-gnu/libc.so.6
     13755:    
     13755:    file=libc.so.6 [0];  generating link map
     13755:      dynamic: 0x00007fbac5cedba0  base: 0x00007fbac592a000   size: 0x00000000003c99a0
     13755:        entry: 0x00007fbac594a950  phdr: 0x00007fbac592a040  phnum:                 10
     13755:    
     13755:    checking for version `GLIBC_2.2.5' in file /lib/x86_64-linux-gnu/libc.so.6 [0] required by file ./hello [0]
     13755:    checking for version `GLIBC_2.3' in file /lib64/ld-linux-x86-64.so.2 [0] required by file /lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    checking for version `GLIBC_PRIVATE' in file /lib64/ld-linux-x86-64.so.2 [0] required by file /lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    
     13755:    Initial object scopes------------------------------(2) 加載動態庫,
     13755:    object=./hello [0]
     13755:     scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
13755:    object=linux-vdso.so.1 [0]


13755:     scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     13755:     scope 1: linux-vdso.so.1
     13755:    
     13755:    object=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:     scope 0: ./hello /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
     13755:    
     13755:    object=/lib64/ld-linux-x86-64.so.2 [0]
     13755:     no scope
     13755:    
     13755:    
     13755:    relocation processing: /lib/x86_64-linux-gnu/libc.so.6 (lazy)
     13755:    symbol=_res;  lookup in file=./hello [0]
     13755:    symbol=_res;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `_res' [GLIBC_2.2.5]
...
     13755:    symbol=__vdso_time;  lookup in file=linux-vdso.so.1 [0]
     13755:    binding file linux-vdso.so.1 [0] to linux-vdso.so.1 [0]: normal symbol `__vdso_time' [LINUX_2.6]
     13755:    symbol=__vdso_gettimeofday;  lookup in file=linux-vdso.so.1 [0]
     13755:    binding file linux-vdso.so.1 [0] to linux-vdso.so.1 [0]: normal symbol `__vdso_gettimeofday' [LINUX_2.6]
     13755:    
     13755:    relocation processing: ./hello (lazy)
     13755:    symbol=__gmon_start__;  lookup in file=./hello [0]
     13755:    symbol=__gmon_start__;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    symbol=__gmon_start__;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
...
     13755:    
     13755:    calling init: /lib/x86_64-linux-gnu/libc.so.6--------(3) 初始化動態庫,
     13755:    
     13755:    symbol=__vdso_clock_gettime;  lookup in file=linux-vdso.so.1 [0]
     13755:    binding file linux-vdso.so.1 [0] to linux-vdso.so.1 [0]: normal symbol `__vdso_clock_gettime' [LINUX_2.6]
     13755:    symbol=__vdso_getcpu;  lookup in file=linux-vdso.so.1 [0]
     13755:    binding file linux-vdso.so.1 [0] to linux-vdso.so.1 [0]: normal symbol `__vdso_getcpu' [LINUX_2.6]
     13755:    symbol=__libc_start_main;  lookup in file=./hello [0]
     13755:    symbol=__libc_start_main;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    binding file ./hello [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `__libc_start_main' [GLIBC_2.2.5]
     13755:    
     13755:    initialize program: ./hello--------------------------(4) 初始化行程,
     13755:    
13755:    
     13755:    transferring control: ./hello------------------------(5) 將程式的控制權交給main函式,
     13755:    
     13755:    symbol=puts;  lookup in file=./hello [0]
     13755:    symbol=puts;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    binding file ./hello [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `puts' [GLIBC_2.2.5]
     13755:    symbol=_dl_find_dso_for_object;  lookup in file=./hello [0]
     13755:    symbol=_dl_find_dso_for_object;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
     13755:    symbol=_dl_find_dso_for_object;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
     13755:    binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to /lib64/ld-linux-x86-64.so.2 [0]: normal symbol `_dl_find_dso_for_object' [GLIBC_PRIVATE]
Hello world!-------------------------------------------------------(6) 執行用戶程式,
     13755:    
     13755:    calling fini: ./hello [0]---------------------------(7) 執行去初始化動作,

6.2 減少加載動態庫的數量

正如《Systems Performance》所說的,最好的優化就是取出不必要的作業,

(1) 將一些無用的動態庫去掉,

(2) 重新組織動態庫的結構,力爭將行程加載動態庫的數量減到最小,

(3) 將一些動態庫編譯成靜態庫,與行程或其他動態庫合并,

優點是:

  • 減少了加載動態庫的數量,
  • 在與其他動態庫合并之后,動態庫內部之間的函式呼叫不必再進行符號查找、動態鏈接,

缺點是:

  • 如果被其他行程或動態庫依賴,則會導致被復制多份,占用更多空間,
  • 失去了代碼段記憶體共享,導致記憶體使用增加,
  • 由于被多個行程使用,導致page fault增多,進而影響加載速度,

因此,對于只被很少行程加載的動態庫,將其編譯成靜態庫,減少行程啟動時加載動態庫的數量,對于那些守護使用的動態庫,代碼段大多已經被加載到記憶體,運行時產生的page
fault要少,因此動態庫反而要比靜態庫速度更快,

(4) 使用dlopen動態加載動態庫,可以精確控制動態庫的生存周期,一方面可以減少動態庫資料段的記憶體使用,另一方面可以減少行程啟動時加載動態庫的時間,

6.3 共享庫的搜索路徑

在行程加載動態庫是,loader要從很多路徑搜索動態庫,搜索順序是:DT_NEED-->DT_RPATH-->LD_LIBRARY_PATH-->LD_RUNPATH-->ld.so.conf-->/lib
/usr/lib,

DT_RPATH和LD_RUNPATH是程式編譯時加的選項,使用-rpath來設定DT_RPATH,

還存在一種比DT_RPATH更高優先級的目錄搜索機制HWCAP,HWCAP是為了支持系統根據不同的硬體特性,道不同的目錄去搜索動態庫,

可以通過屏蔽LD_HWCAP_MASK減少搜索路徑,

export LD_HWCAP_MASK=0X00000000

6.4 動態庫的高度

依據動態庫之間的依賴關系,從當前動態庫到最底層動態庫之間的最長路徑,成為該動態庫的高度,

降低動態庫的高度,有利于提高加載時間,

6.5 動態庫的初始化

在loader完成對動態庫的記憶體應設定后,需要運行動態庫的一些初始化函式,來完成設定動態庫的一些基本環境,包括兩部分:

(1) 動態庫的構造和解構式機制

首先構造三個檔案hello.c、hello.h、main.c,

===========hello.c============
#include <stdio.h>

void __attribute__ ((constructor)) my_init(void)
{
    printf("constructor\n");;  
}

void __attribute__ ((destructor)) my_finit(void)
{
    printf("destructor\n");;  
}

void hello(const char *name)
{
   printf("Hello %s!\n", name);
}

===========hello.h============
#ifndef HELLO_H
 #define HELLO_H
 
 void hello(const char *name);
 
 #endif //HELLO_H
===========main.c============
#include "hello.h"
 
 int main()
 {
  hello("everyone");
  return 0;
 }

然后編譯庫(gcc -fPIC -shared -o libmyhello.so hello.c)、拷貝庫到系統lib目錄(sudo cp
libmyhello.so /usr/lib)、編譯執行檔案(gcc -o hello main.c -L./ -lmyhello),

執行./hello結果如下:

constructor
Hello everyone!
destructor

(2) 動態庫的全域變數初始化作業

在C語言中,全域變數保存在.data段,再啟動程序中,loader只是簡單地使用mmap將資料段映射到dirty
page,這些變數只有在第一次使用到的時候才會為其分配物理記憶體,

從優化的角度來講,要盡量減少全域物件的使用,

6.6 動態鏈接

首先給一段代碼,基于此看看動態鏈接的程序,

#include <stdio.h>

int main()
{
  printf("hello\n");
  return 0;
}

printf是glibc中定義,采用動態庫,在程式編譯階段,編譯器無法得知printf函式地址,

在程式運行時,當呼叫printf的時候,程式會將處理權交給linker,由其負責在執行檔案以及其連接的動態庫中查找printf函式地址,

由于linker不知道printf具體在哪個動態庫,所以將在整個執行檔案和動態庫范圍內查找,

     26221:    
     26221:    runtime linker statistics:
     26221:      total startup time in dynamic loader: 703291 cycles
     26221:            time needed for relocation: 188666 cycles (26.8%)
     26221:                     number of relocations: 77
     26221:          number of relocations from cache: 3
     26221:            number of relative relocations: 1199
     26221:           time needed to load objects: 325593 cycles (46.2%)
hello
     26221:    
     26221:    runtime linker statistics:
     26221:               final number of relocations: 82
     26221:    final number of relocations from cache: 3

可以看出及時簡單列印hello,在啟動程序中查找、鏈接了很多符號,耗費了大量cpu cycle,

優化的方法:

(1) 減少匯出符號的數量

通過去掉那些動態庫中不必匯出的符號,從而減少動態庫在做鏈接時所查找的符號的數量,可以加快動態鏈接的速度,

(2) 減少符號的長度

在做符號鏈接時,linker將做字串的匹配,符號名字越長,其查找匹配的時間越長,

(3) 使用prelink

如果動態庫在編譯的時候就能確定運行時的加載地址,那么動態庫函式呼叫的地址就應該是已知的,在行程運行的時候就沒有必要再進行符號的查找和鏈接,從而節省行程的啟動時間,

6.7 提高行程啟動速度

1. 將行程改為執行緒

2. prefork行程

3. preload行程

4. 提前加載,延后退出

5. 調整CPU的頻率

總體來講,優化行程的啟動速度的順序為:

(1) 優化動態庫的搜索路徑

(2) 檢查行程中是否有無用的動態庫

(3) 減少行程或所依賴動態庫的全域物件的數量

(4) 使用prelink,預先鏈接行程的動態庫

(5) 考慮重新組織動態庫,爭取減少行程加載動態庫的數量

(6) 考慮使用dlopen,將一起啟動時不需要的動態庫從行程的依賴動態庫中去除

如果仍然無法滿足要求,可以采用調度的方法:

(1) 行程改為執行緒

(2) preload行程

(3) 提前加載、延遲退出,

6.8 行程冷起與熱起的區別

在程式第一次啟動(冷起)退出后,再次啟動速度明顯比第一次快,為什么呢?

在程式第一次啟動、退出后,行程雖然被銷毀了,但是行程代碼段所占用的物理記憶體并沒有被銷毀;而是被Linux快取起來,保存在Cache中,

這樣程式再次啟動時,指令不必再從Flash讀到記憶體中,而是直接使用Linux內核中的Cache,減少了程式啟動程序中所產生的page
fault,從而加快了行程的啟動速度,

在行程啟動程序中:

(1) 行程冷起時,如果運行的指令較多,則出現的page fault較多,影響行程的啟動速度,

(2) 行程所依賴的某些動態庫可能已經被一些守護行程所加載,其代碼段已經在記憶體中,故這種動態庫對行程的冷起和熱起性能影響不大,

(3) 沒有被其他行程使用過的動態庫,在冷起時則會產生page fault影響行程的啟動速度,


第7章 性能優化的方法

程式優化!=編碼技巧

編碼技巧是程式優化的一部分;程式優化涉及到硬體架構、程式架構、邏輯設計等,還有一點如何確定代碼瓶頸位置很重要,

7.1 尋找程式熱點

1. gprof

#include <stdio.h>

void funca()
{
  int i = 0, n = 0;

  for(i=0; i<10000000; i++)
  {
    n++;
    n--;
  }
}

void funcb()
{
  int i = 0, n = 0;

  for(i=0; i<10000000; i++)
  {
    n++;
    n--;
  }
}

int main()
{
  int i=0;

  for(i=0;i<10;i++)
  {
    funca();
  }

  funcb();
  return 0;
}

然后編譯(gcc performance.c -pg -o performance)、運行(./performance)、查看結果(gprof
performance gmon.out -q -p),

gprof performance gmon.out -p

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 91.95      0.40     0.40       10    40.46    40.46  funca
  9.20      0.45     0.04        1    40.46    40.46  funcb

=========================================

gprof performance gmon.out -q

             Call graph (explanation follows)


granularity: each sample hit covers 2 byte(s) for 2.25% of 0.45 seconds

index % time    self  children    called     name
                                                 <spontaneous>
[1]    100.0    0.00    0.45                 main [1]
                0.40    0.00      10/10          funca [2]
                0.04    0.00       1/1           funcb [3]
-----------------------------------------------
                0.40    0.00      10/10          main [1]
[2]     90.9    0.40    0.00      10         funca [2]
-----------------------------------------------
                0.04    0.00       1/1           main [1]
[3]      9.1    0.04    0.00       1         funcb [3]
-----------------------------------------------

2. oprofile

7.2 程式邏輯瓶頸

oprofile只能有助于發現熱點,但是對程式熱點與代碼邏輯對應關系無法對應,因此不能定位由邏輯問題所造成的瓶頸,

可以通過添加日志的方法來確定不同邏輯部分耗時,進而找出邏輯問題,

7.3 優化的層次

1. 針對某一特定事例的優化,考慮使用oprofile,查找熱點,進行優化,主要以擴及優化為主、程式熱點函式優化為輔,

2. 系統整體性能的提高,分兩層:業務邏輯的優化和底層基礎函式性能優化,

上層業務邏輯優化:重點在于邏輯的調整、演算法的優化,

底層基礎函式游湖:重點在于代碼的寫作技巧,

7.4 何時開始性能優化

(1) 在需求階段,就要把性能指標定義下來,

(2) 在軟體設計階段,要考慮這些性能指標,根據指標來考慮程式所使用的演算法、邏輯,在這個階段考慮邏輯上的優化,

(3) 在軟體功能基本完成后,一方面軟體的邏輯要做一些細微調整,另一方面要開始使用oprofile之類的工具查找熱點函式,對熱點函式做代碼優化,

7.5 如何推動系統性能優化

(1) 需要找出一些關鍵的步驟,這些步驟性能直接影響著用戶使用體驗,

(2) 為這些關鍵的程序定義相應的性能指標,

(3) 在定義性能指標后,需要測驗現系統,看看各個程序和目標性能之間的差距,

(4) 拿到結果之后嗎,需要和相應開發團隊談判,要求其優化代碼,

當優化任務
陷入僵局的時候,要求相應團隊出具兩份報告:一,從程式邏輯考慮,程式都做了哪些事情,每個事情花了多少時間,主要演算法是什么;二,這個程序中oprofile報告,包括每個函式執行時間占比,查看前幾名函式邏輯上是否合理;對于前幾名函式,檢查其從演算法實作到代碼優化層次是否能夠進行優化,

(5) 在各個團隊優化完代碼之后,回傳流程(3)從新測驗性能,如沒有達標,繼續3~5程序,

7.6 為什么軟體性能會低下

7.7 程式邏輯優化

5個程式優化的思路:

(1) Do it faster:找到最有效率的方法,來提高程式的運行速度,

(2) Do it in parallel:并行加快執行速度,

(3) Do it later:不必要的功能,可以考慮延后執行,騰出資源做重要的事,

(4) Don't do it at all:最好的優化就是不做事,

(5) Do it before:把一些作業空閑時預先完成,


第8章 代碼優化的境界

從高級語言C/C++,到指令在系統上運行,分兩個階段:

(1) 編譯器將C/C++轉變成可以在系統上運行的機器指令,編譯器會對代碼進行優化,優化后的機器指令可能與撰寫的代碼有較大差異,

(2) 機器指令在不同硬體上執行,與體系結構、執行環境有密切關系,

所以優化代碼有兩個境界:從代碼看到編譯器優化后產生的匯編指令;根據芯片組特性,能看到匯編語言在硬體中執行狀態,比如流水線使用情況、快取命中率等等,

8.1 GCC編譯優化

8.1.1 條件編譯

通過宏來降低條件判斷等操作,提高效率,

gcc -DXXX

8.1.2 指定CPU的型號

gcc -mcpu=XXX,是編譯出來的代碼能夠充分利用硬體平臺的特點,加快程式的執行速度,

8.1.3 builtin函式

GCC提供一些builtin函式來完成一些特殊功能,

(1) void *__builtin_apply_args(void);

(1) void __builtin_apply(void (func)(), void *arguments, int size);

(1) void *__builtin_return(void *result);

GCC網站http://gcc.gnu.org/onlinedocs/提供了builtin詳細資訊,

8.1.4 GCC編譯優化

-O0 關閉編譯器優化

-O/-O1 增加了一些GCC優化代碼選項

-O2 除了完成所有-O1級別的優化之外,增加了比如處理器指令調度等,

-O3 除了完成所有-O2級別的優化之外,增加了回圈展開和其他一些處理器特性相關優化作業,

8.2 優化基本原則

8.3 標準C代碼優化

8.4 C++代碼優化

8.5 硬體相關的優化


第9章 系統性能優化

9.1 Shell腳本優化

9.1.1 Shell腳本優化

在嵌入式Linux中,bash腳本占很大比重,優化shell有助于縮短系統啟動時間,加快行程的執行速度,

在Linux bash shell一般由Busybox實作,命令主要被分為兩大類:built-ins和applets,

Built-ins只是簡單的函式呼叫,而applets則意味著需要呼叫"fork/exec"創建子行程來執行,并且busybox也可以使用外部命令,

處于性能考慮,應使用built-ins來代替applets和外部命令,

輸入 busybox ,可以看到支持的所有功能,

include/applets.h中,定義了BusyBox支持的所有功能,

docs/nofork_noexec.txt中,說明了built-ins和applets的區別,

applets.h定義功能的時候,也定義了型別,分類如下:

(1) APPLET:即applets,創建一個子行程,然后呼叫exec執行相應的功能,執行完畢后,返還控制給父行程,

(2) APPLET_NOUSAGE:BusyBox中不包含該命令的幫助檔案,

(3) APPLET_NOEXEC:呼叫fork創建子行程,然后執行BusyBox對應功能,執行完畢后,回傳控制給父行程,

(4) APPLET_NOFORK:相當于built-ins,只執行BusyBox內部函式,不創建子行程,效率最高,

9.1.2 bash腳本

包含在pipe中的built-ins將創建子行程來執行,

包含在'中的命令將創建子行程來執行,

對bash腳本進行優化時,要盡量避免fork行程,

9.1.3 如何優化busybox bansh腳本

(1) 去掉腳本中無用的代碼

(2) 盡可能使用busybox中的built-ins替換外部命令

printf "Starting" --> echo "Starting"

(3) 盡可能不使用pipe

(4) 減少pipe中的命令數

(5) 盡可能不適用" ** ' ** "

更多方法參考: ** Optimize RC Scripts **

9.2 使用preload預先加載行程

在系統比較空閑時,通過將特定程式的代碼從Flash加載到Cache,加快行程執行速度,

能否控制在Cache記憶體回收時,對某些關鍵行程所占用的Cache盡量少回收,加大某一行程Cache記憶體數量,

Linux有一個開源專案 ** preload ** ,就是利用控制Linux中的cache,來加快行程的啟動速度,

類似的技術有prelink和readahead

9.3 調整行程的優先級

在Linux內核中,支持兩種行程:實時行程和普通行程,

(1) 實時行程

實時行程的優先級是靜態設定的,只有當運行佇列沒有實時行程的情況下,普通行程才能夠獲得調度,

實時行程采用兩種調度策略:SCHED_FIFO和SCHED_RR,

FIFO采用先進先出的策略,對于所有相同優先級的行程,最先進入runqueue的行程總能優先獲得調度;Round
Robin采用更加公平的輪轉策略,使得相同優先級的實時行程能夠輪流獲得調度,

對于實時行程來講,使用絕對優先級概念,絕對優先級取值范圍是0~99,數字越大,優先級越高,

(2) 普通行程

Linux 2.6普通行程的絕對優先級取值是0,普通行程有靜態優先級和動態優先級之分,

可以通過nice修改行程的靜態優先級,

系統在運行程序中,在靜態優先級基礎上,不斷動態計算出每個行程的動態優先級,擁有最高優先級的行程被調度器選中,

動態優先級計算公式:動態優先級=max(100,min(靜態優先級-bonus+5,139))

bonus取決于行程的平均睡眠時間,

對實時行程設定通過如下函式進行:

#include <sched.h>
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
int sched_getscheduler(pid_t pid);
int sched_setparam(pid_t pid, const struct sched_param *param);
int sched_getparam(pid_t pid, const struct sched_param *param);

pid:指定所要設定的行程號,pid為0,表示為當前行程,

policy:設定行程調度策略,SCHED_OTHER/SCHED_FIFO/SCHED_RR,

param:設定行程的絕對優先級,范圍是0~99,

對普通行程來講,絕對優先級為0,通過nice來影響行程的調度,

nice取值-20~19,可以通過setpriority來設定普通行程優先級,

#include <sys/resource.h>
int setpriority(int class, int id, int niceval);

class:PRIO_PROCESS/PRIO_PGRP/PRIO_USER,

niceval:為行程nice值,-20~19,

對實時執行緒操作,使用pthread_setschedparam;對普通執行緒,仍然可以使用setpriority和nice來調整執行緒優先級,

9.4 讓行程運行的慢一些

對于某些沒有時限要求的行程,可以降低運行速度,

(1) 降低行程優先級,

(2)
增加一些代碼來控制Linux中行程的調度,如sched_yield自愿放棄CPU,行程仍然處于TASK_RUNNING狀態,但調度器把它放在運行佇列鏈表的末尾,

9.5 守護行程的數量

守護行程占用大量動態庫代碼段和資料段記憶體,記憶體蟹柳概率加大,CPU性能下降,導致系統整體性能下降,

9.6 檔案系統

主要看基于Flash和RAM的兩大類檔案系統,

基于Flash的檔案系統:JFFS2、YAFFS2、Cramfs、Romfs,YAFFS2目前被廣泛運用,

基于RAM的檔案系統:Ramdisk(在Linux啟動,initrd提供將內核映像和根檔案系統一起載入記憶體)、Ramfs/tmpfs(把所有的檔案都放在RAM中),

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/466930.html

標籤:其他

上一篇:stm32F103RCT6使用FFT運算分析波形詳解(非常新手)

下一篇:基于 Xcode 搭建 OpenCV 開發環境

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more