memcached(三)内存管理

memcached使用预申请的方式来管理内存的分配,从而避免内存碎片化的问题。如果采用mallo和free来动态的申请和销毁内存,必然会产生大量的内存碎片。

基本知识

slab:内存块是memcached一次申请内存的最小单元,在memcached中一个slab的默认大小为1M;

slabclass:特定大小的chunk的组。

chunk:缓存的内存空间,一个slab被划分为若干个chunk;

item:存储数据的最小单元,每一个chunk都会包含一个item;

factor:增长因子,默认为1.25,相邻slab中的item大小与factor成比例关系;

基本原理

memcached使用预分配方法,避免频繁的调用malloc和free;

memcached通过不同的slab来管理不同chunk大小的内存块,从而满足存储不同大小的数据。

slab的申请是通过在使用item时申请slab大小的内存空间,然后再把内存切割为大小相同的item,挂在到slab的未使用链表上。

过期和被删除item并不会被free掉,memcached并不会删除已经分配的内存;

Memcached会优先使用已超时的记录空间,通过LRU算法;

memcached使用lazy expiration来判断元素是否过期,所以过期监视上不会占用cpu时间。

源码分析

下面主要分析memcached的内存申请和存储相关代码。

item

item是key/value的存储单元。

typedef struct _stritem {
struct _stritem *next; /* 前后指针用于在链表slab->slots中连接前后数据 */
struct _stritem *prev;
struct _stritem *h_next; /* hash chain next */
rel_time_t time; /* 最后一次访问时间 */
rel_time_t exptime; /* 过期时间 */
int nbytes; /* 数据大小 */
unsigned short refcount; /* 引用次数 */
uint8_t nsuffix; /* suffix长度 */
uint8_t it_flags; /* ITEM_* above */
uint8_t slabs_clsid;/* 所有slab的id */
uint8_t nkey; /* key长度 */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[]; /* cas|key|suffix|value */
} item;

slab初始化

void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - ;
unsigned int size = sizeof(item) + settings.chunk_size; /* 得到每一个item的大小 */ mem_limit = limit; if (prealloc) { /* 预分配一块内存 */
...
} memset(slabclass, , sizeof(slabclass)); /* 把slabclass置为0,slabclass是一个slab数组,存储所有slab的信息 */ while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) { /* 循环初始化每一个slab的内容,保证slab中item的size小于max_size/factor */
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES) /* 用于内存对齐 */
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size; /* 初始化slabclass中item的大小 */
slabclass[i].perslab = settings.item_size_max / slabclass[i].size; /* 初始化每个slab中item的数量 */
size *= factor; /* item的大小随factor逐渐增大 */
...
}
/* 初始化最后一个slab,大小为最大的max_size,只有一个item */
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = ;
...
}

从源码中,可以看出来同一个slab中所有的item的大小都是固定的,

申请slab内存

static void *do_slabs_alloc(const size_t size, unsigned int id) {
slabclass_t *p;
void *ret = NULL;
item *it = NULL; if (id < POWER_SMALLEST || id > power_largest) { /* 判断id是否合法 */
MEMCACHED_SLABS_ALLOCATE_FAILED(size, );
return NULL;
} p = &slabclass[id]; /* 获取slab */
assert(p->sl_curr == || ((item *)p->slots)->slabs_clsid == ); /* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
if (! (p->sl_curr != || do_slabs_newslab(id) != )) { /*如果sl_curr为0,没有剩余的item,那么就执行do_slabs_newslab申请内存空间*/
/* We don't have more memory available */
ret = NULL;
} else if (p->sl_curr != ) { /* 如果有未使用的空间,则获取该item,并从slots链表中删除该item */
/* return off our freelist */
it = (item *)p->slots;
p->slots = it->next;
if (it->next) it->next->prev = ;
p->sl_curr--;
ret = (void *)it;
}
...
return ret;
}

sl_curr来判断是否存在未使用的内容空间,如果不存在需要调用do_slabs_newslab来申请slab空间。

static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
char *ptr;
/* 1. 判断是否超过内存限制
2. 判断是否申请过内存空间
3. 如果没有申请过,则申请slab->size*slab->perslab大小的整块内存
4.如果申请过,调用grow_slab_list来扩大slab大小 */
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) { MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
} memset(ptr, 0, (size_t)len);
split_slab_page_into_freelist(ptr, id); /* 把申请的内存分配到slots链表中 */ p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id); return ;
}

申请空间后,需要通过split_slab_page_into_freelist函数把申请的内存空间分配到未使用的链表中。

static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = ; x < p->perslab; x++) { /* 循环分配内存 */
do_slabs_free(ptr, , id);
ptr += p->size;
}
} static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
...
p = &slabclass[id];
/* 获取内存指针,把item块挂在到slots链表中,增加sl_curr */
it = (item *)ptr;
it->it_flags |= ITEM_SLABBED;
it->prev = ;
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it; p->sl_curr++;
p->requested -= size;
return;
}

获取适当大小的item

在do_item_alloc中,调用了slabs_clsid来获取适合存储当前元素的slab id。

unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST; if (size == )
return ;
while (size > slabclass[res].size) /* 遍历slabclass来找到适合size的item */
if (res++ == power_largest) /* won't fit in the biggest slab */
return ;
return res;
}

优缺点

内存预分配可以避免内存碎片以及避免动态分配造成的开销。

内存分配是由冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就会被丢弃。

由于分配的是特定长度的内存,因此无法有效地利用所有分配的内存,例如如果将100字节的数据存储在128字节的chunk中,会造成28字节的浪费。

memcache(三)内存管理的更多相关文章

  1. memcache的内存管理机制

    Memcache使用了Slab Allocator的内存分配机制:按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题Memcache的存储涉及到slab,page,chunk三 ...

  2. memcache 的内存管理介绍和 php实现memcache一致性哈希分布式算法

    1 网络IO模型 安装memcached需要先安装libevent Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描 ...

  3. How Javascript works (Javascript工作原理) (三) 内存管理及如何处理 4 类常见的内存泄漏问题

    个人总结: 1.两种垃圾回收机制: 1)引用标记算法:如果检测到一个对象没有被引用了,就清除它. ***这种算法不能处理循环引用的情况*** 2)标记—清除算法:从根(全局变量)开始向后代变量检测,任 ...

  4. memcache的内存管理探微

    slab分配器:http://blog.csdn.net/luotuo44/article/details/42737181 hash操作  :http://blog.csdn.net/luotuo4 ...

  5. OC-手动内存管理

    一.为什么要进行内存管理 •移动设备的内存极其有限,每个app所能占用的内存是有限制的 • •下列行为都会增加一个app的内存占用 Ø创建一个OC对象 Ø定义一个变量 Ø调用一个函数或者方法 • •当 ...

  6. 分布式缓存技术memcached学习(三)——memcached内存管理机制

    几个重要概念 Slab memcached通过slab机制进行内存的分配和回收,slab是一个内存块,它是memcached一次申请内存的最小单位,.在启动memcached的时候一般会使用参数-m指 ...

  7. MemCache中的内存管理详解

    MC的内存管理机制 1.内存的碎片化 当我们使用C语言或者其他语言进行malloc(申请内存),free(释放内存)等类似的命令操作内存的时候, 在不断的申请和释放的过程中,形成了一些很小的内存片段, ...

  8. Android 之 内存管理-查看内存泄露(三)

    概述 在android的开发中,要时刻主要内存的分配和垃圾回收,因为系统为每一个dalvik虚拟机分配的内存是有限的,在google的G1中,分配的最大堆大小只有16M,后来的机器一般都为24M,实在 ...

  9. block没那么难(三):block和对象的内存管理

    本系列博文总结自<Pro Multithreading and Memory Management for iOS and OS X with ARC> 在上一篇文章中,我们讲了很多关于 ...

随机推荐

  1. Numpy 学习之路(1)——数组的创建

    数组是Numpy操作的主要对象,也是python数据分析的主要对象,本系列文章是本人在学习Numpy中的笔记. 文章中以下都基于以下方式的numpy导入: import numpy as np fro ...

  2. OS 系统下安装MySql 配置MySql环境变量

    学习Hive需要,闲话不说 本文的内容: 下载Mysql for Mac 下载Mysql Workbench 安装 Mysql 和 Mysql Workbench 配置Mysql在OS 系统上的环境变 ...

  3. jquery之右下角消息提示框

    messager.js (function (jQuery) { var window; var obj = new Object(); obj.version = '@1.0'; obj.title ...

  4. C++基础知识易错点总结(1)

    1. 在C++中,不能被重载的运算符有: sizeof . 成员运算符 .* 成员指针运算符 :: 作用域运算符 ?: 条件运算符 2. C++语言多态性:编译时多态和运行时多态: 编译时多态可通过函 ...

  5. 百度地图开发 Android版应用Key申请

    一 申请API key 在使用百度地图之前,我们必须去申请一个百度地图的API key,申请地址http://lbsyun.baidu.com/apiconsole/key,自己自行注册一个百度账号, ...

  6. 大数据通过PHP快速插入MYSQL的方法

    如果您的mysql是通过brew安装的,那么请 vi /usr/local/Cellar/mysql/5.6.23/my.cnf 将 max_allowed_packet = 64M 写入保存并重启m ...

  7. table表格制作

    分享一个简单的表格,代码如下: <table border=3 bordercolor=blue align=center cellspacing=3 cellpadding=6> < ...

  8. (转)LAMPer技能树

  9. npm ERR publish 403,nodejs发布包流程

    nodejs学习体验之发布包,发布环境如下:1:win10系统,2:已安装nodejs. 具体操作步骤如下: *编写模块 1)新建文件夹,比如:somepackage 2) 该文件夹下新建js文件,比 ...

  10. Samba网络配置

    Samba网络配置 操作环境 ubuntu14.04 1. 更新Linux源列表 sudo apt-get update 2. 安装Samba服务 sudo apt-get install samba ...