從零實(shí)現(xiàn)Nginx風(fēng)格內(nèi)存池完整代碼
1.引言
本文將從為什么需要內(nèi)存池講起,重點(diǎn)探討內(nèi)存池的設(shè)計(jì)哲學(xué),并詳細(xì)拆解其實(shí)現(xiàn)方案,從而搭建起讀者對內(nèi)存池從理論到實(shí)踐的完整認(rèn)知模型。
2.為什么需要內(nèi)存池
為什么需要內(nèi)存池?這個(gè)問題可以換一種問法:為什么不直接malloc/free或new/delete?
我們一層一層的回答這個(gè)問題。
1.malloc/free有較高的性能開銷。 內(nèi)存的申請不光是給我們一塊地址這么簡單,malloc底層還要做很多事情,比如分割內(nèi)存塊,合并碎片等。在多線程場景下,malloc內(nèi)部還會(huì)涉及到鎖競爭,避免給不同線程返回相同的內(nèi)存塊。這些操作,都需要性能開銷。
2.減少系統(tǒng)調(diào)用。 申請內(nèi)存屬于系統(tǒng)級操作,所以會(huì)涉及到用戶態(tài)和內(nèi)核態(tài)的切換,也就不可避免的進(jìn)行CPU的上下文切換。而且,用戶態(tài)有用戶態(tài)的棧,內(nèi)核態(tài)有內(nèi)核態(tài)的棧,不能共用,所以也需要切換。這種用戶態(tài)到內(nèi)核態(tài)的切換,相對于在同一個(gè)態(tài)里直接調(diào)用函數(shù)來說,開銷更高一些。內(nèi)存池通過一次申請大塊內(nèi)存,后面再申請內(nèi)存時(shí)直接從這一大塊中切出來,減少了系統(tǒng)調(diào)用。因此,高性能系統(tǒng)總在想辦法減少系統(tǒng)調(diào)用次數(shù)。
3.減少內(nèi)存碎片。 普通的malloc/free會(huì)產(chǎn)生內(nèi)存碎片。雖然內(nèi)存的總?cè)萘繅颍蔷褪欠峙洳怀鰜頋M足需求的連續(xù)內(nèi)存。內(nèi)存池通過將生命周期一致的對象放一起,比如將一個(gè)請求的所有對象放一起,請求結(jié)束時(shí)將整個(gè)池一起釋放,不需要單獨(dú)的free,這就減少了內(nèi)存碎片。
4.減少鎖競爭。 前面也有提到,多個(gè)線程同時(shí)調(diào)用malloc時(shí),底層是會(huì)有鎖競爭的,但是內(nèi)存池通常是線程私有的,互不影響,幾乎無鎖。
3.內(nèi)存對齊
因?yàn)樵趦?nèi)存池的實(shí)現(xiàn)過程中,涉及到內(nèi)存對齊,所以這里先介紹一下。
內(nèi)存對齊本質(zhì)上是編譯器對變量地址存放位置的一個(gè)強(qiáng)制約束。
為什么需要內(nèi)存對齊?
CPU在讀取數(shù)據(jù)的時(shí)候,是按地址總線的寬度來讀取的。如果數(shù)據(jù)按一定的對齊值進(jìn)行了對齊,那么就可以減少CPU額外的讀取次數(shù),從而提高CPU訪問數(shù)據(jù)的效率。
如何進(jìn)行內(nèi)存對齊?
主要有以下3條規(guī)則:
- 按各自的對齊值對齊。
- 整體按最大對齊值對齊。
- 結(jié)構(gòu)體的大小是最大對齊值的整數(shù)倍。
進(jìn)行內(nèi)存對齊,就一定會(huì)有部分內(nèi)存浪費(fèi)。所以,內(nèi)存對齊本質(zhì)上是一種 空間換時(shí)間 的權(quán)衡之策。
4.內(nèi)存池設(shè)計(jì)的核心思想
因?yàn)榇髮ο笕绻苍趦?nèi)存池中分配的話,很快就會(huì)把內(nèi)存池給沾滿。所以,在設(shè)計(jì)內(nèi)存池時(shí),對小對象和大對象做了區(qū)分。

小對象:從內(nèi)存池中分配內(nèi)存。
大對象:直接malloc分配內(nèi)存。
5.代碼設(shè)計(jì)
小對象的節(jié)點(diǎn)設(shè)計(jì)
struct mp_node_s {
unsigned char *last;
unsigned char* end;
struct mp_node_s *next;
size_t failed;
};last: 下一個(gè)分配的起始地址。下一次有小對象申請內(nèi)存時(shí),從last位置開始向后切出來分配內(nèi)存。
end: 當(dāng)前節(jié)點(diǎn)內(nèi)存的末尾。
next: 指向下一個(gè)節(jié)點(diǎn)的指針。
failed: 記錄當(dāng)前節(jié)點(diǎn)內(nèi)存申請失敗次數(shù)。這個(gè)字段主要是用來做優(yōu)化的,如果該節(jié)點(diǎn)的內(nèi)存申請失敗次數(shù)達(dá)到一定值,我們直接就跳過該節(jié)點(diǎn),不在該節(jié)點(diǎn)中申請內(nèi)存。
大對象的節(jié)點(diǎn)設(shè)計(jì)
struct mp_large_s {
struct mp_large_s *next;
void *alloc;
};next: 指向下一個(gè)節(jié)點(diǎn)的指針。
alloc: 存放的是malloc函數(shù)的返回值,也就是指向的內(nèi)存塊起始地址。
線程池的總體設(shè)計(jì)
struct mp_pool_s {
size_t max;
struct mp_node_s *current;
struct mp_large_s *large;
struct mp_node_s head[];
};max: 小對象和大對象的分界點(diǎn),申請的內(nèi)存大于max的,視為大對象,走的是malloc;申請的內(nèi)存小于max的,視為小對象,走的是內(nèi)存池切塊。
current: 指向小對象鏈表中某個(gè)節(jié)點(diǎn)的指針。current指向哪個(gè)節(jié)點(diǎn),下次分配內(nèi)存時(shí),就首先從該節(jié)點(diǎn)嘗試分配。
large: 指向下一個(gè)大對象節(jié)點(diǎn)的指針。
head: 柔性數(shù)組,整個(gè)小塊鏈表的頭節(jié)點(diǎn)。
這個(gè)結(jié)構(gòu)體,作為整個(gè)內(nèi)存池的入口和管理者。
內(nèi)存池的創(chuàng)建
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void **)&p, MP_ALIGNMENT,
size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if (ret != 0) {
printf("[%s:%d]mp_create_pool failed!\n", __func__, __LINE__);
return NULL;
}
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
// 當(dāng)前可分配的起點(diǎn)
p->head->last = (unsigned char *)p +
sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
retint posix_memalign(void **memptr, size_t alignment, size_t size)
分配一塊地址對齊的內(nèi)存,相對于malloc來說,多了內(nèi)存對齊的功能。

我們除了要分配用戶申請的內(nèi)存大小外,還要給元數(shù)據(jù)申請內(nèi)存。而且,每一個(gè)mp_node_s節(jié)點(diǎn)的大小是一樣的,last指針會(huì)隨著節(jié)點(diǎn)中內(nèi)存的分配情況而移動(dòng)。
小對象節(jié)點(diǎn)的創(chuàng)建
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char* m;
struct mp_node_s *h = pool->head;
//所有擴(kuò)容block大小和初始block保持一致
size_t psize = (size_t)(h->end - (unsigned char *)h);
int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
if (ret != 0) {
printf("[%s:%d]mp_alloc_block failed!\n", __func__, __LINE__);
return NULL;
}
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s *)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m += sizeof(struct mp_node_s);// 跳過node的元數(shù)據(jù)
m = mp_align_ptr(m, MP_ALIGNMENT);//因?yàn)樵獢?shù)據(jù)可能會(huì)破壞內(nèi)存對齊,所以這里需要做一次內(nèi)存對齊
new_node->last = m + size;
current = pool->current;
// 找鏈表的尾部,同時(shí)更新current字段和failed字段,用于表示失敗的次數(shù),以便用作優(yōu)化
for (p = current; p->next; p = p->next) {
if (p->failed > 4) {
current = p->next;
p->failed++;
}
}
p->next = new_node;
pool->current = current ? current : new_node;
return m;
}創(chuàng)建新節(jié)點(diǎn),意味著從current指向的節(jié)點(diǎn)往后的所有節(jié)點(diǎn)的剩余可用空間都小于當(dāng)前用戶申請的內(nèi)存大小。
創(chuàng)建出來的新節(jié)點(diǎn),和前面的節(jié)點(diǎn)大小是一樣的。
大對象的節(jié)點(diǎn)創(chuàng)建
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) {
printf("[%s:%d]mp_alloc_large failed!\n", __func__, __LINE__);
return NULL;
}
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ >= MP_MAX_LARGE_SCAN) break;
}
// large本身不是malloc出來的,而是在內(nèi)存池中分配的
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
printf("[%s:%d]mp_alloc failed!\n", __func__, __LINE__);
return NULL;
}
// 頭插
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}對于大對象,直接malloc分配內(nèi)存。同時(shí),還需要申請節(jié)點(diǎn)本身的空間,這里large節(jié)點(diǎn)的內(nèi)存,不是直接malloc來的,而是走小對象內(nèi)存分配的路,從內(nèi)存池中切割出來的。對于直接malloc出來的大塊內(nèi)存,用完直接free,不用統(tǒng)一釋放。
再強(qiáng)調(diào)一遍,mp_large_s結(jié)構(gòu)體,它內(nèi)部的指針指向malloc出來的大內(nèi)存,但是這個(gè)結(jié)構(gòu)體本身,不是我們直接malloc出來的,而是從內(nèi)存池中申請得到的。因?yàn)?code>mp_alloc函數(shù)內(nèi)部,對于小于一定大小的空間,走的是內(nèi)存池分配,不是malloc。
空間分配函數(shù)
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p->failed++;
p = p->next;
}while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = p->last;
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
}while(p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}外部申請內(nèi)存的時(shí)候,調(diào)用的就是這兩個(gè)函數(shù)??傮w的邏輯就是:小對象走內(nèi)存池分配,大對象直接malloc。
這兩個(gè)函數(shù)的區(qū)別在哪?有沒有內(nèi)存對齊。
mp_alloc是有內(nèi)存對齊的,而mp_nalloc沒有。
CPU會(huì)按固定寬度類型來訪問數(shù)據(jù)的,通常需要內(nèi)存對齊。按字節(jié)流或文本訪問數(shù)據(jù)的,通??梢圆粚R。
我們常見的int、long等這些基本類型,通常就需要內(nèi)存對齊。而字符串就沒必要進(jìn)行內(nèi)存對齊。
內(nèi)存池的銷毀
void mp_destroy_pool(struct mp_pool_s *pool) {
struct mp_node_s *head = pool->head->next;
struct mp_node_s *next = NULL;
struct mp_large_s *cur = pool->large;
while (cur) {
if (cur->alloc) free(cur->alloc);
cur = cur->next;
}
while (head) {
next = head->next;
free(head);
head = next;
}
free(pool);
}如果有大對象的內(nèi)存沒有釋放,那么這里會(huì)一并釋放,最后在釋放這個(gè)內(nèi)存池。
6.內(nèi)存池完整代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define MP_ALIGNMENT 32
#define MP_PAGE_SIZE 4096
#define MP_MAX_LARGE_SCAN 4
#define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
#define mp_align_ptr(p, alignment) \
(void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
struct mp_large_s {
struct mp_large_s *next;
void *alloc;
};
struct mp_node_s {
unsigned char *last;
unsigned char* end;
struct mp_node_s *next;
size_t failed;
};
struct mp_pool_s {
size_t max;//內(nèi)存池允許的小塊分配上限
struct mp_node_s *current;
struct mp_large_s *large;
struct mp_node_s head[];
};
void *mp_alloc(struct mp_pool_s *pool, size_t size);
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void **)&p, MP_ALIGNMENT,
size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if (ret != 0) {
printf("[%s:%d]mp_create_pool failed!\n", __func__, __LINE__);
return NULL;
}
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
// 當(dāng)前可分配的起點(diǎn)
p->head->last = (unsigned char *)p +
sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
void mp_destroy_pool(struct mp_pool_s *pool) {
struct mp_node_s *head = pool->head->next;
struct mp_node_s *next = NULL;
struct mp_large_s *cur = pool->large;
while (cur) {
if (cur->alloc) free(cur->alloc);
cur = cur->next;
}
while (head) {
next = head->next;
free(head);
head = next;
}
free(pool);
}
void mp_reset_pool(struct mp_pool_s *pool) {
struct mp_large_s *cur = pool->large;
struct mp_node_s *head = pool->head;
while (cur) {
free(cur->alloc);
cur = cur->next;
}
pool->large = NULL;
while (head) {
head->last = mp_align_ptr((unsigned char *)
head + sizeof(struct mp_node_s), MP_ALIGNMENT);
head->failed = 0;
head = head->next;
}
pool->current = pool->head;
}
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char* m;
struct mp_node_s *h = pool->head;
//所有擴(kuò)容block大小和初始block保持一致
size_t psize = (size_t)(h->end - (unsigned char *)h);
int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
if (ret != 0) {
printf("[%s:%d]mp_alloc_block failed!\n", __func__, __LINE__);
return NULL;
}
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s *)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m += sizeof(struct mp_node_s);// 跳過node的元數(shù)據(jù)
m = mp_align_ptr(m, MP_ALIGNMENT);//因?yàn)樵獢?shù)據(jù)可能會(huì)破壞內(nèi)存對齊,所以這里需要做一次內(nèi)存對齊
new_node->last = m + size;
current = pool->current;
// 找鏈表的尾部,同時(shí)更新current字段和failed字段,用于表示失敗的次數(shù),以便用作優(yōu)化
for (p = current; p->next; p = p->next) {
if (p->failed > 4) {
current = p->next;
p->failed++;
}
}
p->next = new_node;
pool->current = current ? current : new_node;
return m;
}
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) {
printf("[%s:%d]mp_alloc_large failed!\n", __func__, __LINE__);
return NULL;
}
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ >= MP_MAX_LARGE_SCAN) break;
}
// large節(jié)點(diǎn)不是malloc出來的,而是在small pool分配的
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
printf("[%s:%d]mp_alloc failed!\n", __func__, __LINE__);
return NULL;
}
// 頭插
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {
void *p;
int ret = posix_memalign(&p, alignment, size);
if (ret != 0) {
printf("[%s:%d]mp_memalign failed!\n", __func__, __LINE__);
return NULL;
}
struct mp_large_s *large = mp_alloc(pool, size);
if (large == NULL) {
free(p);
printf("[%s:%d]mp_memalign failed!\n", __func__, __LINE__);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
/**
* brief:帶內(nèi)存對齊的空間分配函數(shù)
*/
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
}while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
/**
* brief:不帶內(nèi)存對齊的空間分配函數(shù),因?yàn)椴皇撬袌鼍跋露夹枰獌?nèi)存對齊
*/
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = p->last;
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
}while(p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
void *p = mp_alloc(pool, size);
if (p) memset(p, 0, size);
return p;
}
void mp_free(struct mp_pool_s *pool, void *p) {
struct mp_large_s *cur;
for (cur = pool->large; cur; cur = cur->next) {
if (p == cur->alloc) {
free(cur->alloc);
cur->alloc = NULL;
return;
}
}
}
int main() {
int size = 1 << 12;
struct mp_pool_s *pool = mp_create_pool(size);
for (int i = 0; i < 10; i++) {
void *mp = mp_alloc(pool, 512);
}
for (int i = 0; i < 5; i++) {
void *mp = mp_alloc(pool, 5120);
mp_free(pool, mp);
}
mp_reset_pool(pool);
for (int i = 0; i < 50; i++) {
void *mp = mp_alloc(pool, 512);
}
mp_destroy_pool(pool);
return 0;
}7.整體結(jié)構(gòu)圖

8.結(jié)語
到此這篇關(guān)于從零實(shí)現(xiàn)Nginx風(fēng)格內(nèi)存池的文章就介紹到這了,更多相關(guān)Nginx內(nèi)存池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例詳解SpringBoot+nginx實(shí)現(xiàn)資源上傳功能
這篇文章主要介紹了SpringBoot+nginx實(shí)現(xiàn)資源上傳功能,由于小編最近在使用nginx放置靜態(tài)資源問題,遇到很多干貨,特此分享到腳本之家平臺,供大家參考,需要的朋友可以參考下2019-10-10
深度剖析Nginx限速模塊的3個(gè)核心陷阱與5種正確實(shí)踐
這篇文章主要為大家詳細(xì)介紹了Nginx中限速模塊的3個(gè)核心陷阱與5種正確實(shí)踐,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-11-11
使用Nginx搭建代理服務(wù)器(正向代理HTTPS網(wǎng)站)的操作指南
在網(wǎng)絡(luò)應(yīng)用中,代理服務(wù)器是用于中轉(zhuǎn)用戶請求和服務(wù)端響應(yīng)的工具,正向代理主要用于客戶端與外部服務(wù)器之間的訪問代理,幫助客戶端隱藏其 IP 地址或訪問受限資源,本文將詳細(xì)介紹如何使用 Nginx 搭建正向代理服務(wù)器,特別是針對 HTTPS 網(wǎng)站的代理2024-11-11
解決Nginx 配置 proxy_pass 后 返回404問題
這篇文章主要介紹了Nginx 配置 proxy_pass 后 返回404問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01

