共计 3046 个字符,预计需要花费 8 分钟才能阅读完成。
应用程序通过与 Zend MM(Zend Memory Manager)交互,来间接实现对操作系统内存的应用、屏蔽掉操作系统细节,简化了开发,并提供内存池技术,性能失去大幅晋升。
三种内存对应三种粒度:
- Huge(chunk): 占用 2M,一个 chunk 由 512 个 page 组成。申请内存大于 2M 时,间接调用零碎调配,调配若干个 chunk
- Large(page): 占用 4K。申请内存大于 3092B(3/4 page_size),小于 2044KB(511 page_size) 时,调配若干个 page
- Small(slot): 申请内存小于等于 3092B(3/4 page_size),内存池提前定义好了 30 种等同大小的内存 (8,16,24,32,…3072),他们调配在不同的 page 上 (不同大小的内存可能会调配在多个间断的 page),申请内存时间接在对应 page 上查找可用地位
面包店比喻:chunk 相当于面粉仓库,page 相当于一袋面粉,slot 相当于做好的小面包。顾客购买面包时,优先从 slot 中抉择匹配的,如果不满足,则再从新和面甚至从仓库取面粉。同时有一个准则:返回满足需要 size 的最小规格,如申请 7k 内存,会返回 8k 内容,申请 30k 内存,会返回 32k。
三种尺寸通过 zend_mm_heap 构造保留,三者间的关系:
调配过程:
在 Zend/zend_alloc_sizes.h 中定义了 small 内存的 size,第一列从 0 到 29 示意一共 30 种 size,第二列从 8 字节到 3072 字节示意每种 size 的大小,第三列示意预留了多少个这种 size,第四列示意须要用多少个 page。
/* num, size, count, pages */
#define ZEND_MM_BINS_INFO(_, x, y) \
_(0, 8, 512, 1, x, y) \
_(1, 16, 256, 1, x, y) \
_(2, 24, 170, 1, x, y) \
_(3, 32, 128, 1, x, y) \
_(4, 40, 102, 1, x, y) \
_(5, 48, 85, 1, x, y) \
_(6, 56, 73, 1, x, y) \
_(7, 64, 64, 1, x, y) \
_(8, 80, 51, 1, x, y) \
_(9, 96, 42, 1, x, y) \
_(10, 112, 36, 1, x, y) \
_(11, 128, 32, 1, x, y) \
_(12, 160, 25, 1, x, y) \
_(13, 192, 21, 1, x, y) \
_(14, 224, 18, 1, x, y) \
_(15, 256, 16, 1, x, y) \
_(16, 320, 64, 5, x, y) \
_(17, 384, 32, 3, x, y) \
_(18, 448, 9, 1, x, y) \
_(19, 512, 8, 1, x, y) \
_(20, 640, 32, 5, x, y) \
_(21, 768, 16, 3, x, y) \
_(22, 896, 9, 2, x, y) \
_(23, 1024, 8, 2, x, y) \
_(24, 1280, 16, 5, x, y) \
_(25, 1536, 8, 3, x, y) \
_(26, 1792, 16, 7, x, y) \
_(27, 2048, 8, 4, x, y) \
_(28, 2560, 8, 5, x, y) \
_(29, 3072, 4, 3, x, y)
zend_mm_heap 构造体
struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
int use_custom_heap;
#endif
#if ZEND_MM_STORAGE
zend_mm_storage *storage;
#endif
#if ZEND_MM_STAT
size_t size; /* current memory usage */
size_t peak; /* peak memory usage */
#endif
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
size_t real_size; /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
size_t real_peak; /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
size_t limit; /* memory limit */
int overflow; /* memory overflow flag */
#endif
zend_mm_huge_list *huge_list; /* list of huge allocated blocks */
zend_mm_chunk *main_chunk;
zend_mm_chunk *cached_chunks; /* list of unused chunks */
int chunks_count; /* number of alocated chunks */
int peak_chunks_count; /* peak number of allocated chunks for current request */
int cached_chunks_count; /* number of cached chunks */
double avg_chunks_count; /* average number of chunks allocated per request */
#if ZEND_MM_CUSTOM
union {
struct {void *(*_malloc)(size_t);
void (*_free)(void*);
void *(*_realloc)(void*, size_t);
} std;
struct {void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
} debug;
} custom_heap;
#endif
};
在申请 chunk 和 page 时,必须是整数倍。如 2M 4M 等,4K 8K 12K 等。
small 内存调配过程
比方语句 $a = [1, 2, 3];
用到了 31 字节,小于 3K,走 small 内存申请。
通过 zend_mm_small_size_to_bin 函数,计算应该从下面的 ZEND_MM_BINS_INFO 中取 num3 的行,即 32 字节,应该用 1 个 page。
从 chunk list 上第 1(page_num)个地位,拿出 1 个 page,分成 128 份,每份 32 字节,调配给变量后,其余的挂在 mm_heap 的 free_slot 前面。下次再有须要 32 字节尺寸的内存,间接取用即可,不必从新申请了。
内存池的内存对齐
内存对齐会节约一点空间,但会进步读写效率。
chunk 是以最小 2M 调配的,每次调配时为保障内存对齐,申请 4M 内存地址,这样申请的空间横跨 2 个 2M,之后掐头去尾,留下两头的残缺 2M 地址,实现内存对齐。又因为 mmap 治理的最小单位是页即 4K 大小,这样能够做一步优化,每次 chunk 申请时,申请 4M-4k 大小的内存。
开释内存
先辨别是 small、large、huge 三种中哪一种内存,不同类型所占用空间大小和起始地位不同,能够计算出具体应该开释的地位。其中 small 内存开释后,还能够挂到 free_slots 上之后再用。
本文由 mdnice 多平台公布