swoole之memoryGlobal内存池分析
内存池的作用:
直接使用系统调用malloc会有如下弊端:
- 频繁分配内存时会产生大量内存碎片
- 频繁分配内存增加系统调用开销
- 容易造成内存泄漏
内存池是预先申请一定数量的,大小相等的内存块作为预备使用;当需要时向内存池分出一部分内存,若内存块不够使用时再向系统申请新的内存块,下面就swoole的swMemoryGlobal内存池作为分析例子
swoole swMemoryPool 数据结构设计
swMemoryGlobal是swoole内存池实现一种方式,学习内存池主要是要掌握其数据结构的设计,memoryGlobal实现如下:
// src/memory/MemoryGlobal.c
typedef struct _swMemoryPool
{
void *object; // 指向swMemoryGlobal指针
void* (*alloc)(struct _swMemoryPool *pool, uint32_t size); // 分配内存函数指针
void (*free)(struct _swMemoryPool *pool, void *ptr); // 是否内存函数指针
void (*destroy)(struct _swMemoryPool *pool); // 销毁内存函数指针
} swMemoryPool;
typedef struct _swMemoryGlobal
{
uint8_t shared;
uint32_t pagesize; // 指定每个swMemoryGlobal_page需要申请内存大小
swLock lock; // 互斥锁
swMemoryGlobal_page *root_page; // 指向第一个swMemoryGlobal_page指针,有头指针可以销毁内存池
swMemoryGlobal_page *current_page; // 指向当前swMemoryGlobal_page指针
uint32_t current_offset;
} swMemoryGlobal;
typedef struct _swMemoryGlobal_page
{
struct _swMemoryGlobal_page *next; // 指向下一个节点
char memory[0]; // 这是一个柔性数组,用于记录申请内存后的内存地址
} swMemoryGlobal_page;
这三者之间的关系如下:

swMemoryPool
swMemoryPool可以看做是一个类,它提过了alloc,free,destory方法,以及object属性,object实际上是指向swMemoryGlobal的指针,而alloc,free,destory
则是对object操作,即通过alloc,free,destory操作swMemoryGlobal上的内容,例如:
```
// src/core/base.c
//init global shared memory
SwooleG.memory_pool = swMemoryGlobal_new(SW_GLOBAL_MEMORY_PAGESIZE, 1);
SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(SwooleGS_t));
```
以上代码是分配sizeof(SwooleGS_t)大小内存
swMemoryGlobal
swMemoryGlobal维护着一个链表,每个节点即swMemoryGlobal_page,root_page指向第一个节点,current_page指向当前节点,pagesize指为一个节点申请
内存大小,current_offset则表示一个节点已被使用内存
swMemoryGlobal_page
swoole根据swMemoryGlobal.pagesize申请指定大小的内存,如下:
```
// src/memory/MemoryGlobal.c
swMemoryGlobal_page *page = swMemoryGlobal_new_page(&gm);
```
上面说过swMemoryGlobal_page是一个链表节点,这里需要说明的是第一个节点,第一个节点的current_offset为sizeof(swMemoryGlobal) + sizeof(swMemoryPool);
而并非为0;如下代码,当为第一个swMemoryGlobal_page申请内存后,立马就为swMemoryPool和swMemoryGlobal分配内存
```
// src/memory/MemoryGlobal.c
gm.pagesize = pagesize;
// 系统申请一个pagesize大小内存
swMemoryGlobal_page *page = swMemoryGlobal_new_page(&gm);
if (page == NULL)
{
return NULL;
}
if (swMutex_create(&gm.lock, shared) < 0)
{
return NULL;
}
gm.root_page = page;
// page->memory为空闲内存
gm_ptr = (swMemoryGlobal *) page->memory;
gm.current_offset += sizeof(swMemoryGlobal);
// swMemoryPool指向空闲内存偏移地址,占用sizeof(swMemoryPool)内存
swMemoryPool *allocator = (swMemoryPool *) (page->memory + gm.current_offset);
gm.current_offset += sizeof(swMemoryPool);
allocator->object = gm_ptr;
allocator->alloc = swMemoryGlobal_alloc;
allocator->destroy = swMemoryGlobal_destroy;
allocator->free = swMemoryGlobal_free;
// 将gm写入到gm_ptr,即空闲内存前sizeof(gm)用于swMemoryGlobal
memcpy(gm_ptr, &gm, sizeof(gm));
<h3>分配内存</h3>
<p>分配内存由swMemoryGlobal_alloc方法执行;该方法为swMemoryPool一个函数指针,如下</p>
allocator->alloc = swMemoryGlobal_alloc; // 分配方法
// src/core/base.c
//init global shared memory
SwooleG.memory_pool = swMemoryGlobal_new(SW_GLOBAL_MEMORY_PAGESIZE, 1);
SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(SwooleGS_t));
// src/memory/MemoryGlobal.c
static void *swMemoryGlobal_alloc(swMemoryPool *pool, uint32_t size)
{
swMemoryGlobal *gm = pool->object;
gm->lock.lock(&gm->lock);
if (size > gm->pagesize - sizeof(swMemoryGlobal_page)) // sizeof(swMemoryGlobal_page)为swMemoryGlobal_page类型的指针大小
{
swWarn("failed to alloc %d bytes, exceed the maximum size[%d].", size, gm->pagesize - (int) sizeof(swMemoryGlobal_page));
gm->lock.unlock(&gm->lock);
return NULL;
}
// 如果一个节点不够分配内存,则重新申请一个新节点,并设置当前节点current_page为新节点
if (gm->current_offset + size > gm->pagesize - sizeof(swMemoryGlobal_page))
{
swMemoryGlobal_page *page = swMemoryGlobal_new_page(gm);
if (page == NULL)
{
swWarn("swMemoryGlobal_alloc alloc memory error.");
gm->lock.unlock(&gm->lock);
return NULL;
}
gm->current_page = page;
}
void *mem = gm->current_page->memory + gm->current_offset;
gm->current_offset += size;
gm->lock.unlock(&gm->lock);
// 结果返回空闲内存的偏移地址
return mem;
}
<h3>柔性数组</h3>
<p>柔性数组(0长度数组)作用: 为了满足需要变长度的结构体(结构体是可变长的)</p>
<ul>
<li>数组名不占用空间,分配的内存是连续的</li>
<li>不会像定长数组一样浪费空间</li>
<li>不会像指针一样需要分别分配内存,分别释放内存</li>
</ul>
<p>定长数组使用方便, 但是却浪费空间, 指针形式只多使用了一个指针的空间, 不会造成</p>
<p><a href="https://github.com/wuzhc/zcnote/blob/master/php/swoole/memory_global.md" rel="nofollow noreferrer">我的笔记</a><br><a href="http://www.360doc.com/content/17/1119/10/9200790_705214752.shtml" rel="nofollow noreferrer">柔性数组参考</a></p>
原文地址:https://segmentfault.com/a/1190000016679265
swoole之memoryGlobal内存池分析的更多相关文章
- nginx 内存池分析
最近nginx的源码刚好研究到内存池,这儿就看下nginx内存池的相关的东西. 一,为什么要使用内存池 大多数的解释不外乎提升程序的处理性能及减小内存中的碎片,对于性能优化这点主要体现在: (1)系统 ...
- nginx源码分析—内存池结构ngx_pool_t及内存管理
Content 0. 序 1. 内存池结构 1.1 ngx_pool_t结构 1.2 其他相关结构 1.3 ngx_pool_t的逻辑结构 2. 内存池操作 2.1 创建内存池 2.2 销毁内存池 2 ...
- nginx源代码分析之内存池实现原理
建议看本文档时结合nginx源代码. 1.1 什么是内存池?为什么要引入内存池? 内存池实质上是接替OS进行内存管理.应用程序申请内存时不再与OS打交道.而是从内存池中申请内存或者释放内存到内存池 ...
- STL源码分析之内存池
前言 上一节只分析了第二级配置器是由多个链表来存放相同内存大小, 当没有空间的时候就向内存池索取就行了, 却没有具体分析内存池是怎么保存空间的, 是不是内存池真的有用不完的内存, 本节我们就具体来分析 ...
- leveldb源码分析之内存池Arena
转自:http://luodw.cc/2015/10/15/leveldb-04/ 这篇博客主要讲解下leveldb内存池,内存池很多地方都有用到,像linux内核也有个内存池.内存池的存在主要就是减 ...
- linux内存源码分析 - 内存池
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的 ...
- nginx源码分析——内存池
内存池的目的就是管理内存,使回收内存可以自动化一些. ngx_palloc.h /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- 对象池与.net—从一个内存池实现说起
本来想写篇关于System.Collections.Immutable中提供的ImmutableList里一些实现细节来着,结果一时想不起来源码在哪里--为什么会变成这样呢--第一次有了想写分析的源码 ...
随机推荐
- Server初见——python
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))p ...
- IETF透露HTTP over QUIC 将重命名为HTTP/3 协议
周一,IETF透露它将HTTP-over-QUIC实验协议重命名为HTTP / 3.HTTP-over-QUIC是一种HTTP重写,用TCP替换TCP. 如果这看起来有点为时过早,那么它与IETF的历 ...
- u-boot for tiny210 ver1.0(by liukun321咕唧咕唧)
新版本下载: 下面的链接提供了较新版本的源码 ver4.0源码下载:u-boot for tiny210 ver4.0 ver3.1源码下载: u-boot for tiny210 ver3.1 v ...
- ofbiz初级教程
本教程是ofbiz 基本应用,它涵盖了OFBiz应用程序开发过程的基本原理.目标是使开发人员熟悉最佳实践,编码惯例,基本控制流程以及开发人员对OFBiz定制所需的所有其他方面. 本教程将帮助您在OFB ...
- POJ 1430
上面的估计是题解吧....呃,如果真要用到公式的话,确实没听过.... #include <iostream> #include <cstdio> #include <a ...
- 设计网页录入信息与自己定义server数据接收
需求:设计一个注冊网页用于录入username和登录password.并将数据传入server并显示出来. 1.前言:网页提交的 get 和 post 两种方式. (1)对于get提交方式,以本文中样 ...
- Centos yum 安装lamp PHP5.4版本号
centos 6.5 1.yum安装和源码编译在使用的时候没啥差别.可是安装的过程就大相径庭了,yum仅仅须要3个命令就能够完毕,源码须要13个包,还得加压编译.步骤非常麻烦,并且当做有时候会出错,源 ...
- Java多线程之~~~线程安全容器的非堵塞容器
在并发编程中,会常常遇到使用容器.可是假设一个容器不是线程安全的.那么他在多线程的插入或者删除的过程 中就会出现各种问题.就是不同步的问题.所以JDK提供了线程安全的容器,他能保证容器在多线程的情况下 ...
- Linux网络编程(附1)——封装read、write
原打算实践简单的模型的时候,主要专注于基本的模型,先用UNIX I/O糊弄下,可是未封装的read和write用起来实在心累,还是直接用前辈们已经实现好的高级版本号read.write. UNIX I ...
- Windows环境下通过Git来管理自己的Android代码
前面已经介绍了在Windows下使用git工具来下载Android的源代码,Windows环境下通过Git得到Android源代码,这里记录我使用git工具来管理我自己的代码,git是一种分布式的项目 ...