避免內存碎片

    許多書籍提到過內存碎片,也看到一些方法防治內存碎片。一直以來都以爲頻繁的分配釋放內存會導致系統內存碎片過多(雖然這個想法並沒有錯到離譜)。後來看過計算機程序設計藝術上面關於夥伴系統的介紹,一般操作系統都採用此種方法來管理內存。頻繁分配釋放內存確實會導致一些系統負擔,但分配的內存釋放及時,內存管理系統將能夠急時合併相鄰空閒內存塊,得到更大的空閒內存。這樣並不會導致內存碎片的出現。即使相鄰空間不空閒,這樣產生的碎片還是比較少的


  今天突然醒悟內存碎片的出現主要跟分配有關,特別是分配小而且生命週期很長的內存塊時,才容易導致內存碎片的出現。對於夥伴系統,假設有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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章