`

避免内存碎片

 
阅读更多

     许多书籍提到过内存碎片,也看到一些方法防治内存碎片。一直以来都以为频繁的分配释放内存会导致系统内存碎片过多(虽然这个想法并没有错到离谱)。后来看 过计算机程序设计艺术上面关于伙伴系统的介绍,一般操作系统都采用此种方法来管理内存。频繁分配释放内存确实会导致一些系统负担,但分配的内存释放及时, 内存管理系统将能够急时合并相邻空闲内存块,得到更大的空闲内存。这样并不会导致内存碎片的出现。即使相邻空间不空闲,这样产生的碎片还是比较少的


  今天突然醒悟内存碎片的出现主要跟分配有关,特别是分配小而且生命周期很长的内存块时,才容易导致内存碎片的出现。对于伙伴系统,假设有16byte空 间,依次分配一个1,3,5byte空间,在分配4byte,对于伙伴系统将分配不出最后的4byte,尽管还有7byte的额外空间,但这些空间只剩下 1、2byte的空间块,如下图,即使这些伙伴空间都是空闲的,也难以被充分利用。而3、5byte分配后剩下的空间更是由于少于上层空间的一半而被浪费 掉了,不能再进行分配。如果分配的小块内存相当多,将会浪费很多内存空间,导致内存碎片化。当然真正操作系统是32位对齐的,但情行是类似的


 
  所以如果要动态分配的空间比较小,一般采取先分配一大块空间。然后在有内存分配需求时,从大块空间依次取出。如vc中的map list array 等便是如此设计。每个类都先使用CPlex 分配一定数量的CAssoc空间,当空间用完后,在分配相同大小的空间。当然这些空间是链接在一起的。下面是从quake3中摘出来的一部分代码,对于 quake配置参数是一直存在于整个软件运行期的,这些参数依次保存在smallzone空间中。


  如果分配的空间很快就会释放(如分配释放同时在一个函数内),那么就不需要考虑内存碎片问题。但是鉴于伙伴系统效率,如果存在大量频繁的分配释放,可以考 虑使用桶状内存管理。即分配一块大的内存zone(当然还需要3个指针,标志zone头尾和当前写入的位置)。而后需要相对小的空间时,直接向zone中 写入,如到zone尾部时,转到zone开头写入。当然以前写的东西就被覆盖了,一定要保证覆盖时这段内存中的东西已经不再需要了。如果想更省事可以考虑 boost pool 内存池,不过 pool 每次分配的块大小总是固定的

 void func()
 {
      boost::pool<> p(256*sizeof(BYTE));   //指定每次分配的块的大小
      for (int i = 0; i < 10000; ++i)
      {
        BYTE* const  p = p.malloc();  //pool分配指定大小的内存块;需要的时候,pool会向系统申请大块内存。
        ... // Do something with t; 
        p.free( p);        //释放内存块,交还给pool,不是返回给系统。
      }
      //pool的析构函数会释放所有从系统申请到的内存。
}

//注意必须是.c文件。
#define ZONEID 0x1d4a11
typedef enum
{
     TAG_FREE,
     TAG_GENERAL,
     TAG_BOTLIB,
     TAG_RENDERER,
     TAG_SMALL,
     TAG_STATIC
} memtag_t;

typedef struct memblock_s
{
     int size;           // including the header and possibly tiny fragments
     int     tag;            // a tag of 0 is a free block
     struct memblock_s       *next, *prev;
     int     id;         // should be ZONEID
} memblock_t;

memzone_t *mainzone;
memzone_t *smallzone;


//只调用一次分配足够内存空间
void Com_InitSmallZoneMemory( void ) {
     s_smallZoneTotal = 512 * 1024;
     smallzone = calloc( s_smallZoneTotal, 1 );
     if ( !smallzone )
     {
          Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal /(1024*1024) );
     }
     Z_ClearZone( smallzone, s_smallZoneTotal ); 
     return;
}

//从大的内存空间中逐步取出足够的小块空间
void *S_Malloc( int size)
 {
     int  extra, allocSize;
     memblock_t *start, *rover, *new, *base;
     memzone_t *zone;

     tag =TAG_SMALL;
     zone = smallzone;

     allocSize = size;
     size += sizeof(memblock_t); // account for size of block header
     size += 4;   // space for memory trash tester
     size = (size + 3) & ~3;  // align to 32 bit boundary 
     base = rover = zone->rover;
    start = base->prev;
 
     do
     {
          if (rover == start) 
          {
                Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",        size, zone == smallzone ? "small" : "main");
               return NULL;
          }
      if (rover->tag)
      {
           base = rover = rover->next;
      } else
      {
           rover = rover->next;
      }
     } while (base->tag || base->size < size);
     extra = base->size - size;
     if (extra > MINFRAGMENT) {
      // there will be a free fragment after the allocated block
      new = (memblock_t *) ((byte *)base + size );
      new->size = extra;
      new->tag = 0;   // free block
      new->prev = base;
      new->id = ZONEID;
      new->next = base->next;
      new->next->prev = new;
      base->next = new;
      base->size = size;
     }
 
     base->tag = tag;   // no longer a free block 
     zone->rover = base->next; // next allocation will start looking here
     zone->used += base->size; // 
    base->id = ZONEID;
    *(int *)((byte *)base + base->size - 4) = ZONEID;
    return (void *) ((byte *)base + sizeof(memblock_t));
}

void Z_ClearZone( memzone_t *zone, int size ) {
     memblock_t *block; 
     // set the entire zone to one free block
     zone->blocklist.next = zone->blocklist.prev = block =
      (memblock_t *)( (byte *)zone + sizeof(memzone_t) );
     zone->blocklist.tag = 1; // in use block
     zone->blocklist.id = 0;
     zone->blocklist.size = 0;
    zone->rover = block;
    zone->size = size;
    zone->used = 0; 
    block->prev = block->next = &zone->blocklist;
    block->tag = 0;   // free block
    block->id = ZONEID;
    block->size = size - sizeof(memzone_t);
}

//向内存块中添加字符串
char *CopyString( const char *in )
{
     char *out;
     if (!in[0]) {
      return ((char *)&emptystring) + sizeof(memblock_t);
     }
     else if (!in[1])
     {
          if (in[0] >= '0' && in[0] <= '9')
          {
               return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
         }
    }
    out = S_Malloc (strlen(in)+1);
    strcpy (out, in);
    return out;
}

分享到:
评论

相关推荐

    内存碎片处理技术

    内存碎片处理技术 内存碎片是一个很棘手的问题。如何分配内存决定着内存碎片是否会、何时会、如何会成为一个问题。...有些软件环境,如 OSE 实时操作系统已经备有避免内存碎片的良好工具,但个别程序

    内存碎片原理

    内存碎片产生的原理和如何避免产生碎片的产生。

    dlmalloc_malloc_dlmalloc_

    高效的内存管理功能,有效避免内存碎片,快捷、高效是主要特点

    Microsoft.IO.RecyclableMemoryStream:一个为.NET MemoryStream对象提供池以提高应用程序性能的库

    Microsoft.IO.RecyclableMemoryStream ...避免内存碎片 允许多种方式读取和写入数据,以避免不必要的分配 提供出色的可调试性和日志记录 提供性能跟踪指标 特征 语义与原始System.IO.MemoryStream实现非常接近

    java8集合源码-oak:用于大数据分析的可扩展并发键值映射

    它还通过避免内存碎片来实现缓存友好性。 . OakMap 从堆外获取键和数据,因此允许使用巨大的堆 (RAM) — 甚至超过 50G — 没有 JVM GC 开销。 为了支持堆外,OakMap 具有嵌入式、高效、基于 epoch 的内存管理,主要...

    java8stream源码-Oak:用于大数据分析的可扩展并发键值映射

    它还通过避免内存碎片来实现缓存友好性(请参阅 参考资料)。 OakMap 从堆外获取键和数据,因此允许使用巨大的堆 (RAM) — 甚至超过 50G — 没有 JVM GC 开销。 为了支持堆外,OakMap 具有嵌入式、高效、基

    内存管理内存管理内存管理

    内存管理内幕 dragonimp's blog coder.developer.[designer].ArchitecturE.manager.^_^... posts - 29, comments - 121, trackbacks - 27 My Links Home Contact Login News !!! Article ...

    为什么航空系统的嵌入式软件都禁止动态分配内存(含源码)

    C 库函数 - malloc() 函数简介 ...为什么避免使用 内存有限,多次申请不易管理 碎片 内存泄漏 怎么解决 正确使用malloc函数分配内存 正确使用free函数释放内存 自定义一套内存分配器 最后

    C++ new分配内存失败 内存碎块

    不停的new和delete,有可能会导致分配一个较大的内存块时失败,主要是因为内存碎片。  如果new出稍大块的内存,失败一定会发生,原因在于内存碎片太多后,当在申请一块大内存时,系统无法分配一块连续的没有使用过...

    HashIndex:内建内存池的内存索引结构,面向特定场景业务数据,比如在线广告业务数据

    面向每次都是Fixed-size的内存申请操作场景设计,可进行有效的内存回收,提高内存使用效率,避免产生内存碎片。不是面向任意size的内存申请场景设计##Hash_index.h正排索引结构。Not thread-safe。open-hash. 默认...

    嵌入式系统的自适应动态内存分配算法

    基于此提出了一种新的适用与嵌入式系统的动态内存管理方案,在融合了经典算法精髓的同时,通过引入特殊的数据结构,避免了常用算法某些方面的不足,使其更能满足嵌入式系统对内存管理的特殊需求。

    操作系统(内存管理)

    文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 为什么必须管理内存 内存管理是计算机编程最为基本的...

    CLR编程,CLR 原理,CLR 内幕

    这样,CLR就消除了出现内存泄漏和内存碎片的可能性。CLR提供的堆管理和整理机制被称作垃圾回收(garbage collection)-- 垃圾即被抛弃的变量和对象,CLR管理的堆被称为可回收垃圾的堆(garbage-collected heap)。在C++/...

    扩展的内存管理机制设计实验.zip

    在了解实时嵌入式操作系统内存管理机制的特点以及实时处理对内存管理需求的基础上,练习并掌握有效处理内存碎片的内存管理机制,同时理解防止内存泄漏问题的良好设计方法。 管理系统是一种通过计算机技术实现的用于...

    memoptimizer:memotimtimizer-用于主动空闲内存管理的用户空间守护程序

    根据当前的可用页面消耗率和内存碎片,它可以预测系统是否很可能用完了内存,或者在不久的将来内存是否会严重碎片化。 如果是这样,如果系统将要用尽内存,它将调整水印以强制回收内存。 如果预测内存将严重碎片化...

    汇编语言开发重难点总结.docx

    合理规划内存布局,减少内存碎片。 避免跨段访问,减少段间转移开销。 条件判断: 使用分支指令的短格式或有条件转移指令优化跳转逻辑。 尽可能减少分支预测失败的情况。 中断处理: 快速保存和恢复寄存器状态,...

    数据库连接池

    在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。 2. 更快的系统响应速度 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用...

    ZIP压缩程序源代码

    使用C++对开源的ZIP压缩源程序进行了改写,增加了将字符串直接输入到ZIP压缩文件的代码。为了避免产生内存碎片,不使用动态内存申请函数。

    jemalloc.NET:.NET的本机内存管理器

    jemalloc是“通用的malloc(3)实现,它强调避免碎片和可扩展的并发支持”,已在业界,特别是在必须大量内存的应用程序中。 除了碎片和并发优化之外,jemalloc还提供了一系列用于调试,监视和调整分配的开发人员...

Global site tag (gtag.js) - Google Analytics