基础优化策略

内存页的最小粒度为4K或16K,所以尽量分配其整数倍大小,避免浪费内存。

  • 延迟分配&懒分配
MyGlobalInfo* GetGlobalBuffer() {
    static MyGlobalInfo* sGlobalBuffer = NULL;
    if ( sGlobalBuffer == NULL ) {
            sGlobalBuffer = malloc( sizeof( MyGlobalInfo ) );
     }
     return sGlobalBuffer;
}
  • 高效初始化内存
    malloc分配的小块内存,并不会保证清零初始化,一般会配上memset来初始化。但memset会强制将虚拟内存映射到触发物理内存,如果短时间内并不需要写入数据,会额外增加内存开销。而calloc会保留要分配的虚拟地址空间,直到真正使用的时候才会映射到物理内存并清零初始化,而且只是初始化要用到的内存页。所以建议使用calloc代替malloc+memset
  • 复用频繁使用的大内存
    如果你的计算需要频繁创建大的临时buffer,可以考虑复用buffer而不是每次重新分配。即使每次使用的buffer大小可能不一样,也可以通过realloc方法来扩展已有Buffer。多线程环境下,最好将Buffer放入线程私有存储里thread-local storage,避免多个线程同时操作同一Buffer。
    缓存了Buffer减少了内存分配的次数,但也可能造成Footprint长期较大,所以仅适合需频繁分配Buffer的场景。
  • 及时释放无用的内存
    及时释放无用的内存,尤其要清理内存泄露问题。
  • 小内存分配
    malloc分配内存块的最小粒度为16字节,举例,当你需要分配4字节时,malloc返回的是16字节的内存块;当你需要24字节时,返回的是32字节的内存块(16的整数倍)。所以我们设计数据结构时尽量占用16字节的整数倍。
  • 大内存分配
    malloc分配大内存时(包含多个内存页),会自动使用vm_allocate来获取内存,而该过程只是分配了虚拟地址空间,并未立即分配对应的物理内存。当代码想要读写该内存区域某个地址时,会触发缺页错误,此时内核会进行以下操作:
  • 从可用页(free list)获取一页,并清零初始化。
  • 将该物理页记录到VM Object的resident pages中。
  • 通过修改叫做pmap的结构体, 将虚拟页映射到物理页。(pmap包含了CPU/MMU用来映射地址的页表)
  • 批量分配
    如果需要分配多个同等大小的内存块,可以使用malloc_zone_batch_malloc ,它比多次调用malloc要高效的多,尤其当内存块较小时(<4K)。这个方法会尽力分配请求的块数,但最终返回的块数可能少于请求的,所以需要仔细判断返回结果。
  • 批量释放
    所有内存分配都是在某个zone范围内,zone可以理解为一段可变大小的虚拟内存。你可以在zone里分配多个内存块,然后一次性释放整个zone,比单独释放每个内存块要高效的多。
  • 延迟拷贝
    通过memcpymemmove 拷贝内存叫做即时拷贝,源内存块和目标内存块需要同时存在内存中。当拷贝较大内存块时,增加了应用的整体内存占用和内存换出的几率。
    如果拷贝完内存后并不需要里面使用,可以使用vm_copy实现延迟拷贝。vm_copy并不创建真实的内存块,而是通过修改虚拟内存映射,来表示目标内存区域是源内存区域的一个写时复制版本。为了实现延迟拷贝,内核需要将源内存页从虚拟内存空间中清理掉(物理内存还在)。下一次进程再访问源内存页时,会触发soft fault,内核会将该内存页重新映射回虚拟内存。处理soft fault跟即时拷贝性能损耗差不多,所以只在发生拷贝后长时间不再访问数据的情况下优势明显。
  • iOS低内存告警
    iOS的虚拟内存没有换出磁盘的机制,所以需要依赖应用去释放内存。当iOS的可用内存页少于某阈值后,会尝试释放未修改的内存页(Clean Memory),如果需要的话也会终结一些切换到后台的应用。过程中可能也会向运行中的应用发送低内存告警通知。应用收到该通知时,需要尽可能清理不必要的内存。比如根页面为UITabViewCtroller的应用,可以先移除未展示的Tab, 下次需要展示时再重新加载。
    清理缓存数据时要注意 Memory Compression 带来的影响,避免清理已经压缩过的数据时触发了解压操作,反而增加了内存。建议用 NSCache 代替 NSDictionary,使用 NSPurgableData 代替 NSData

NSCache 分配的内存实际上是 Purgeable Memory,可以由系统自动释放。NSCache 与 NSPureableData 的结合使用既能让系统根据情况回收内存,也可以在内存清理的同时移除相关对象。