专栏首页欧阳大哥的轮子iOS标准库中常用数据结构和算法之内存池

iOS标准库中常用数据结构和算法之内存池

⛲️内存池

内存池提供了内存的复用和持久的存储功能。设想一个场景,当你分配了一块大内存并且填写了内容,但是你又不是经常去访问这块内存。这样的内存利用率将不高,而且无法复用。而如果是采用内存池则可以很轻松解决这个问题:你只需要从内存池中申请这块内存,设置完内容后当不需要用时你可以将这块内存放入内存池中,供其他地方在申请时进行复用,而当你再次需要时则只需要重新申请即可。内存池提供了内存分配编号而且设置脏标志的概念,当你把分配的内存放入内存池并设置脏标志后,系统就会在适当的时候将这块内存的内容写回到磁盘,这样当你再次根据内存编号来访问内存时,系统就又会从磁盘中将内容读取到内存中去。

功能:在iOS中提供了一套内存池管理的API,你可以用这套API来实现上述的功能,而且系统内部很多功能也是借助内存池来实现内存复用和磁盘存储的。 头文件: #include <mpool.h> 平台: iOS系统,BSD系统。

一、内存池的创建、同步和关闭

功能:创建和关闭一个内存池对象并和磁盘文件绑定以便进行同步操作。 函数签名

//创建一个内存池对象
 MPOOL * mpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache);

//将内存池中的脏数据同步写回到磁盘文件中
int mpool_sync(MPOOL *mp);

//关闭和销毁内存池对象。
int mpool_close(MPOOL *mp);

参数: key:[in] 保留字段,暂时没有用处,传递NULL即可。 fd:[in] 内存池关联的磁盘文件句柄,文件句柄需要用open函数来打开。 pagesize:[in] 内存池中每次申请和分配的内存的尺寸大小,单位是字节。 maxcache:[in] 内存池中内存页的最大缓存数量。如果池中缓存的内存数量超过了最大缓存的数量就会复用已经存在的内存,而不是每次都分配新的内存。 return:[out] 返回一个新创建的内存池对象,其他两个函数成功返回0,失败返回非0.

描述

  1. 内存池中的内存的分配和获取是以页为单位进行的,每次分配的页的尺寸大小由pagesize指定,同时内存池也指定了最大的缓存页数量maxcache。每次从内存池中分配一页内存时,除了会返回分配的内存地址外,还会返回这一页内存的编号。这个编号对于内存池中的内存页来说是唯一的。因为内存池中的内存是可以被复用的,因此就有可能是不同的编号的内存页所得到的内存地址是相同的。
  2. 每一个内存池对象都会要和一个文件关联起来,以便能够实现内存数据的永久存储和内存的复用。文件句柄必须用open函数来打开,比如下面的例子:
 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);
  1. 当我们不需要使用某个内存页时或者内存页的内容有改动则我们需要将这个内存页放入回内存池中,并将页标志为脏(DIRTY)。这样系统就会在适当的时候将此内存页的数据写回到磁盘文件中,同时此内存页也会在后续被重复利用。
  2. 当我们想将所有设置为脏标志的内存页立即写入磁盘时则需要调用mpool_sync函数进行同步处理。
  3. 当我们不再需要内存池时,则可以通过mpool_close来关闭内存池对象,需要注意的是关闭内存池并不会将内存中的数据回写到磁盘中去。
二、内存池中内存的获取

功能: 从内存池中申请分配一页新的内存或者获取现有缓存中的内存。 函数签名:

//从内存池中申请分配一页新的内存
void *  mpool_new(MPOOL *mp, pgno_t *pgnoaddr);
//根据内存编号页获取对应的内存。
void * mpool_get(MPOOL *mp, pgno_t pgno, u_int flags);

参数: mp:[in] 内存池对象。 pgnoaddr:[out] 用于mpool_new函数,用于保存新分配的内存页编号。 pngno:[in] 用于mpool_get函数,指定要获取的内存页的编号。 flags:[in] 此参数暂时无用。 return:[out] 返回分配或者获取的内存地址。如果分配或者获取失败则返回NULL。 描述:

  1. 无论是new还是get每次从内存池里面分配或者获取的内存页的大小都是由上述mpool_open函数中的pagesize参数指定的大小。
  2. 系统内部分配的内存是用calloc函数实现的,但是我们不需要手动调用free来对内存进行释放处理。
  3. 每个内存页都有一个唯一的页编号,而且每次分配的页编号也会一直递增下去。
  4. mpool_new函数申请分配新的内存时,如果当前缓存中的内存页小于maxcache数量则总是分配新的内存,只有当缓存数量大于maxcache时才会从现有的缓存中寻找一页可以被重复利用的内存页,如果没有可以重复利用的页面,则会继续分配新的内存页。
  5. mpool_get函数则根据内存页的编号获取对应的内存页。如果编号不存在则返回NULL。需要注意的是一般在获取了某一页内存后,不要进行重复获取操作,否则在DEBUG状态下会返回异常。另外一个情况是有可能相同的页编号下两次获取的内存地址是不一样的,因为系统实现内部有内存复用的机制。
三、内存池中内存的放回

功能:将分配或者申请的内存页放回到内存池中去,以便进行重复利用。 函数签名:

int  mpool_put(MPOOL *mp, void *pgaddr, u_int flags);

参数: mp: [in] 内存池对象。 pgaddr:[in] 要放入缓存的内存页地址。这个地址由mpool_get/new两个函数返回。 flags:[in] 放回的属性,一般设置为0或者MPOOL_DIRTY。 return:[in] 函数调用成功返回0,失败返回非0

描述

  1. 这个函数用来将内存页放入回内存池缓存中,以便对内存进行重复利用。当将某个内存地址放入回缓存后,将不能再次访问这个内存地址了。如果要想继续访问内存中的数据则需要借助上述的mpool_get/new函数来重新获取。
  2. flags:属性如果指定为0时,表明放弃这次内存中的内容的修改,系统不会将内存中的内容写入到磁盘中,而只是将内存放入缓存中供其他地方重复使用。而如果设置为MPOOL_DIRTY时,则表明将这页内存中的数据设置为脏标志,除了同样将内存放入缓存中重复利用外,则会在适当的时候将内存中的数据写入到磁盘中,以便下次进行读取。
四、实例代码
 //创建并打开一个文件。
 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);

//创建一个内存池对象,每页的内存100个字节,最大的缓存数量为4
 MPOOL *pool = mpool_open(NULL, fd, 100, 4);

   
//从内存池中分配一个新的内存页,这里对返回的内存填写数据。
 pgno_t pidx1, pidx2 = 0;
 char *mem1 =  (char*)mpool_new(pool, &pidx1);
 memcpy(mem1, "aaa", 4);
    
 char *mem2 = (char*)mpool_new(pool, &pidx2);
 memcpy(mem2, "bbb", 4);
    
//将分配的内存mem1放回内存池中,但是内容不保存到磁盘
 mpool_put(pool, mem1, 0);
//将分配的内存mem2放回内存池中,但是内容保存到磁盘。
 mpool_put(pool, mem2, MPOOL_DIRTY);
    
//经过上面的操作后mem1,mem2将不能继续再访问了,需要访问时需要再次调用mpool_get。   
mem1 = (char*)mpool_get(pool, pidx1, 0);
mem2 =   (char*)mpool_get(pool, pidx2, 0);

//上面的mem1和mem2可能和前面的new返回的地址是不一样的。因此在内存池中不能通过地址来做唯一比较,而应该将编号来进行比较。
       
//将所有设置为脏标志的内存也写回到磁盘中去。
 mpool_sync(pool);

 mpool_close(pool);  //关闭内存池。

 close(fd);  //关闭文件。

内存池为iOS系统底层开发提供了一个非常重要的能力,我们可以好好利用内存池来对内存进行管理,以及一些需要进行持久化的数据也可以借助内存池来进行保存,通过内存池提高内存的重复利用率。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Thunk程序的实现原理以及在iOS中的应用(二)

    thunk程序其实就是一段代码块,这段代码块可以在运行时动态构造也可以在编译时构造。thunk程序除了在第一篇文章中介绍的用途外还可以作为某些真实函数调用的跳板...

    欧阳大哥2013
  • 深入iOS系统底层之XCODE对汇编的支持介绍

    一个好的IDE不仅要提供舒适简洁和方便的源代码编辑环境,还要提供功能强大的调试环境。XCODE是目前来说对iOS应用开发支持的最好的IDE(虽然Visual S...

    欧阳大哥2013
  • Swift5.0的Runtime机制浅析

    Objective-C语言是一门以C语言为基础的面向对象编程语言,其提供的运行时(Runtime)机制使得它也可以被认为是一种动态语言。运行时的特征之一就是对象...

    欧阳大哥2013
  • 浅析JAVA中堆内存与栈内存的区别

    存放基本类型的变量,对象的引用和方法调用,遵循先入后出的原则。 栈内存在函数中定义的“一些基本类型的变量和对象的引用变量”都在函数的栈内存中分配。当在一段代码块...

    三哥
  • SQL Server内存

    背景 最近一个客户找到我说是所有的SQL Server 服务器的内存都被用光了,然后截图给我看了一台服务器的任务管理器。如图 ? 这里要说明一下任务管理器不会完...

    用户1217611
  • 故障分析 | MySQL OOM 故障应如何下手

    前阵子处理这样一个案例,某客户的实例 mysqld 进程内存经常持续增加导致最终被 OOM killer。作为 DBA 肯定想知道有哪些原因可能会导致 OOM(...

    爱可生开源社区
  • zephyr笔记 2.3.2 内存池

    内存池是一个内核对象,它允许从指定的内存区域动态分配内存块。 内存池中的内存块可以具有任意大小,从而在应用程序需要为不同大小的数据结构分配存储空间时减少浪费的内...

    twowinter
  • iOS内存优化心得

    当app经过一段儿时间的迭代,往往会出现一些性能问题,这时能够协助开发同学解决这些性能问题也成为我们测试同学的重要工作。凑巧最近一段时间小编就一直在协助开发同学...

    用户5521279
  • 虚拟内存详解

    究其原因,监控系统计算的可用内存算法有偏差,他只关注了计算机的“实际”内存,忽略了计算机的虚拟内存。

    bisal
  • 软件性能测试(连载9)

    Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。Linux的空间又分为内核空间和用户空间,在32位中,内核空间占1G,用户空间...

    小老鼠

扫码关注云+社区

领取腾讯云代金券