[筆記] 解碼Nginx:記憶體池(Memory Pool)
解碼Nginx:記憶體池(Memory Pool)
樑濤(@無鋒之刃)
2012-11-17 ~ 2012-11-25
nginx-1.2.5
原始碼檔案
src/core/ngx_palloc.h
src/core/ngx_palloc.c
背景
對於HTTP伺服器而言,“每秒處理請求數”是非常關鍵的效能指標。處理路徑上每一步動作的時間開銷,都將對該指標造成影響。特別地,記憶體分配/釋放作為最基礎的動作,對總處理時間施加的影響更為巨大——想想那些伴隨複雜資料結構或細碎邏輯產生的記憶體分配/釋放動作,很可能在單次處理中執行數十次,假設每次動作都呼叫malloc()/free()函式,時間開銷將頗為可觀、無法接受。由此,Nginx針對HTTP應用的業務特點,重新設計出一套記憶體管理機制,以記憶體池的形式提供給上層結構使用,從而有效地優化效能。
根據分配/釋放頻繁程度,HTTP應用的記憶體使用特徵可以劃分為兩類:
- 程式初始化時分配、終止時釋放的大塊記憶體,供應給配置檔案等全程不會變化的資料結構使用;
- 處理單次請求時反覆分配/釋放的小塊記憶體,供應給字串處理等小型結構體和臨時資料使用。
對於前者,使用malloc()/free()帶來的時間開銷並無大礙;對於後者,一旦使用完後即刻呼叫free()釋放記憶體,則會產生許多不必要的時間開銷。更理想的作法是將零碎分配得來的記憶體收集起來,在某個時間點集中釋放掉,亦即“多次分配,一次釋放”。通過設計得當的資料結構和介面,可以在確保“誰分配,誰釋放”的記憶體使用原則下,提供更好的效能。
記憶體管理模型
Nginx將記憶體管理模型組織為兩層結構的記憶體池:
- 第一層結構採用“向系統請求儲備記憶體塊(多次)——按需分配零碎記憶體塊(多次)——集中釋放零碎記憶體塊(單次)”的設計思路,每次分配不超過某個固定長度上限的零碎記憶體塊;
- 第二層結構採用簡單的“向系統請求獨立記憶體塊(多次+轉發)”的設計思路,亦即轉發分配請求給malloc(),以分配第一層不負責管理的更大塊的獨立記憶體塊。
除此以外,還包裝了malloc()/free()以及更為底層的memalign()/posix_memalign(),用以分配不歸記憶體池管理的記憶體塊。
第一層結構
ngx_create_pool() ngx_palloc()/ngx_pnalloc() ...
| |
| \- ngx_palloc_block()
| |
\- ngx_memalign() ------\ \- ngx_memalign() ------\
| |
| |
ngx_pool_t V ngx_pool_data_t V
+-------------------+ /----> +-----------------+ /-> ...
| ngx_pool_data_t d | | | last | --\ |
| +----------------+ | +-----------------+ | |
| | last | --\ | | end | -----\ |
| +----------------+ | | +-----------------+ | | |
| | end | -----\ | | next | --------/
| +----------------+ | | | +-----------------+ | |
| | next | --------+ | failed | | |
| +----------------+ | | | +-----------------+ | |
| | failed | | | | | allocated | | |
+--+----------------+ | | | | area | | |
| max | | | | /~~~~~~~~~~~~~~~~~/ <-/ |
+-------------------+ | | | / unallocated / |
| current | --------/ | area | |
+-------------------+ | | | | |
| chain | -----------\ | | |
+-------------------+ | | | | | |
| large | --------\ | +-----------------+ <----/
+-------------------+ | | | |
/--- | cleanup | | | | |
| +-------------------+ | | | |
| | log | | | | |
| +-------------------+ | | | \-> ???
| | allocated | | | |
| | area | | | |
| /~~~~~~~~~~~~~~~~~~~/ <-/ | \----> 第二層結構
| / unallocated / | (ngx_pool_large_t)
| | area | |
| | | |
| | | |
| | | |
| +-------------------+ <----/
|
|
| ngx_pool_cleanup_t
\--> +-------------------+
| handler |
+-------------------+
| data |
+-------------------+
/--- | next |
| +-------------------+
|
| ngx_pool_cleanup_t
\--> +-------------------+
| handler |
+-------------------+
| data |
+-------------------+
/--- | next |
| +-------------------+
|
|
\--> ...
注意點:
1. ngx_pool_t和ngx_pool_data_t結構體存放在分配出的儲備記憶體塊首址處,佔用固定空間;
2. 記憶體長度大於max欄位值的分配請求將轉發給第二層結構處理;
3. 使用ngx_pool_data_t結構體構造出單向連結串列,管理各儲備記憶體塊;
4. 使用ngx_pool_cleanup_t結構體構造出單向連結串列,管理各清理回撥函式;
5. current欄位總是指向failed計數值不超過4的第一個ngx_pool_data_t結構體。
第二層結構
ngx_palloc()/ngx_pnalloc() ngx_palloc()/ngx_pnalloc()
| |
\- ngx_palloc_large() --\ \- ngx_palloc_large() --\
| |
ngx_pool_large_t V ngx_pool_large_t V
+-------------------+ /-----> +-------------------+ /-----> ...
| alloc | | | alloc | |
+-------------------+ | +-------------------+ |
| next | ------/ | next | ------/
+-------------------+ +-------------------+
注意點:
1. ngx_pool_large_t結構體是從ngx_pool_data_t結構管理的儲備記憶體塊中分配出來的,並且會適當複用。
介面函式
ngx_create_pool(size, log)
ngx_create_pool()負責建立記憶體池,動作序列如下:
- 向系統請求size引數指定大小的儲備記憶體塊(按NGX_POOL_ALIGNMENT對齊,預設16位元組),邏輯上劃分為(ngx_pool_t結構體+剩餘記憶體區)兩部分;
- 初始化ngx_pool_data_t結構體各個欄位(參考前一節,last欄位指向初始分配地址);
- 計算單次分配的記憶體塊長度上限(max欄位,最大不超過size引數值或NGX_MAX_ALLOC_FROM_POOL);
- 初始化其它欄位(current欄位指向自己)。
ngx_destroy_pool(pool)
ngx_destroy_pool()負責銷燬記憶體池,動作序列如下:
- 呼叫各個清理回撥函式;
- 釋放各個ngx_pool_large_t結構體管理的儲備記憶體塊;
- 釋放各個ngx_pool_data_t結構體管理的獨立記憶體塊。
ngx_reset_pool(pool)
ngx_reset_pool()負責釋放零碎記憶體塊,動作序列如下:
- 釋放各個ngx_pool_large_t結構體管理的記憶體塊;
- 簡單地將各個ngx_pool_data_t結構體的last欄位復原成初始分配地址,而不是釋放。
ngx_palloc(pool, size)/ngx_pnalloc(pool, size)/ngx_pcalloc(pool, size)
ngx_palloc()負責分配零碎記憶體塊,動作序列如下:
- 若size引數大於max欄位值,轉發給ngx_palloc_large()處理(呼叫更底層的分配函式);
- 以current欄位所指ngx_pool_data_t結構體為起點,在單向連結串列中搜尋第一個能執行分配動作的結構體,完成分配(分配首址按NGX_ALIGNMENT對齊);
- 若以上動作均告失敗,轉發給ngx_palloc_block()處理(先分配新的ngx_pool_data_t結構體,再分配零碎記憶體塊)。
ngx_pnalloc()動作與ngx_palloc()類似,但不對齊分配首址。
ngx_pcalloc()將分配請求轉發給ngx_palloc()處理,並對返回的零碎記憶體塊進行清零初始化。
ngx_palloc_block(pool, size)
ngx_palloc_block()負責向系統請求新的儲備記憶體塊,最終完成分配,動作序列如下:
- 向系統請求指定大小的儲備記憶體塊(按NGX_POOL_ALIGNMENT對齊,預設16位元組),邏輯上劃分為(ngx_pool_data_t結構體+剩餘記憶體區)兩部分;
- 初始化各個欄位(包括分配首址,按NGX_ALIGNMENT對齊);
- 調整current欄位的指向(實際上維持著一個“... 5 5 5 4 3 2 1 0 0”的failed欄位值序列),將新的ngx_pool_data_t結構體掛入單向連結串列中。
ngx_palloc_large(pool, size)/ngx_pmemalign(pool, size, alignment)
ngx_palloc_large負責分配不歸第一層結構管理的獨立記憶體塊,動作序列如下:
- 呼叫ngx_alloc()分配新的獨立記憶體塊;
- 搜尋第一個空閒的ngx_pool_large_t結構體,儲存並返回獨立記憶體塊首址;
- 若前一步失敗,從本記憶體池中分配新的ngx_pool_large_t結構體並加入單向連結串列中,儲存並返回獨立記憶體塊首址。
ngx_pmemalign()動作與ngx_palloc_large()類似,但會按alignment引數對齊獨立記憶體塊首址。
ngx_pfree(pool, p)
ngx_pfree()負責“釋放”記憶體塊,動作序列如下:
- 如果待“釋放”物件是獨立記憶體塊,則轉呼叫ngx_free()釋放。
實際上該函式並不真正釋放零碎記憶體塊,而是儘可能將釋放動作推遲到ngx_reset_pool()/ngx_destroy_pool()中。
ngx_pool_cleanup_add(p, size)
ngx_pool_cleanup_add()負責分配新的ngx_pool_cleanup_t結構體,以便呼叫端註冊回撥函式。動作序列如下:
- 從本記憶體池中分配一個ngx_pool_cleanup_t結構體;
- 若size引數不為0,嘗試從本記憶體池分配相應大小的一塊記憶體並作為回撥引數;
- 將新的結構體掛到單向連結串列中;
- 調整cleanup欄位,保持回撥函式按“先進後出”的性質(類似於壓棧操作)。
ngx_pool_run_cleanup_file(p, fd)/ngx_pool_cleanup_file(data)/ngx_pool_cleanup_file(data)
ngx_pool_run_cleanup_file()負責搜尋註冊在回撥函式連結串列中的、與fd引數指定檔案控制程式碼對應的回撥函式並呼叫之。
ngx_pool_cleanup_file()提供一個關閉指定檔案控制程式碼的預設實現函式。
ngx_pool_delete_file()提供一個刪除檔案並關閉相關檔案控制程式碼的預設實現函式。
未完待續...
相關文章
- iOS Memory 記憶體詳解 (長文)iOS記憶體
- Swoole 原始碼分析——記憶體模組之記憶體池原始碼記憶體
- Memory記憶體傳值記憶體
- 【Mysql】讀書筆記之--innodb_buffer_pool記憶體的管理MySql筆記記憶體
- Allowed memory size 記憶體不足記憶體
- Unity Memory Profiler 記憶體分析Unity記憶體
- GC最佳化:棧記憶體、span、NativeMemory、指標、池化記憶體 筆記GC記憶體指標筆記
- 【Mysql】Mysql額外記憶體池 innodb_additional_mem_pool_sizeMySql記憶體
- [筆記] 解碼Nginx:列表(List)筆記Nginx
- MySQL入門--記憶體buffer poolMySql記憶體
- shared pool記憶體結構記憶體
- Oracle記憶體分配與使用小記(二)Shared Pool and Large PoolOracle記憶體
- 記憶體池設計記憶體
- 記憶體管理(Debug Memory Graph)記憶體
- 12. 記憶體管理(Memory Management)記憶體
- Linux 記憶體池原始碼淺析Linux記憶體原始碼
- leveldb程式碼精讀 記憶體池Arena記憶體
- 記憶體池、程式池、執行緒池記憶體執行緒
- mongo記憶體筆記Go記憶體筆記
- [筆記] 解碼Nginx:陣列(Array)筆記Nginx陣列
- nginx共享記憶體分析Nginx記憶體
- eclipse Memory Analyzer(MAT) 記憶體分析Eclipse記憶體
- Netty原始碼解析 -- 記憶體池與PoolArenaNetty原始碼記憶體
- WWDC筆記-記憶體策略筆記記憶體
- 虛擬記憶體筆記記憶體筆記
- 記憶體(memory)表和臨時(temporary)表之瞭解記憶體
- C++記憶體管理:簡易記憶體池的實現C++記憶體
- 記憶體池原理大揭祕記憶體
- C++手寫記憶體池C++記憶體
- 照片記憶編寫軟體:Memory Pictures for MacMac
- LevelDB學習筆記 (3): 長文解析memtable、跳錶和記憶體池Arena筆記記憶體
- Composer 記憶體不足解決方案 PHP Fatal error: Out of memory記憶體PHPError
- 一行程式碼教你解決FlutterPlatformViews記憶體洩露(memory leak)行程FlutterPlatformView記憶體洩露
- ORACLE記憶體管理 之五 SGA variable pool,shared_pool,large_pool,java_poolOracle記憶體Java
- ABAP Memory Inspector 裡對動態記憶體物件的記憶體消耗度量方式記憶體物件
- SQL Server 記憶體洩露(memory leak)——遊標導致的記憶體問題SQLServer記憶體洩露
- [筆記] 解碼Nginx:雙向佇列(Queue)筆記Nginx佇列
- 筆記-更深層次的瞭解iOS記憶體管理筆記iOS記憶體