【C语言】内存池管理,分段维护,以bitmap形式存储,碎片利用,优化内存开销。

本文介绍了如何使用C语言实现位图存储优化,通过位运算减少内存开销。文章提供了两种实现方式,一种是预先计算每个段的起始位置、移位数和间隙,性能更优但有额外内存开销;另一种则不存储额外信息,但在计算时有重复计算。通过实例展示了如何设置和获取位图中的值,并进行了性能对比。建议在内存优化时考虑使用第一种实现。

背景:
1,在C语言中,如果一个状态只有true和false,通常会用一个字节保存。当系统中有几十上百个类似状态是,往往需要开销几百个字节, 对于这种开销,如果改成1个bit存储, 那么会缩小8倍。
2,对于状态不只是true和false的, 假设需要3bit,4bit或者5bit,更或者更多,对于一个很大的系统,如一个进程中开辟了1000个线程,每个线程中需要维护100个1~32状态机,那么通常会开销1000 * 100个字节。
分析:1~32可以用5bit存储,存储值0~31加上1个偏移量1即可,这样一个状态机可以节约3bit,每个线程可以节省100*3/8=37.5字节。1000个线程可以节省37500/1024KB=36.6KB.

基于此,用BitMap的形式, 能够优化不少内存。有相当的收益。

模型:

 

C语言实现(性能更优):

(1)优点:初始时刻根据最大分段数目和没一段的bit长度计算出每一段的起始位置,每一段的移位数和间隙。在设置的时候只需要根据段索引查表,性能更有,减少不必要的重复计算。

(2)缺点:有额外内存开销。

#include <stdio.h>
#include <stdlib.h>

typedef unsigned int        WORD32;
typedef unsigned char       UCHAR;

#define GET_TYPE_BIT_LEN(type)   (sizeof((type)) << 3)
#define DWORD_BIT_DIVISOR        5
#define DWORD_BIT_LEN_MASK       0x1F
#define DWORD_MAX_BIT_NUM        32

#define DWORD_INDEX(bits)        ((bits) >> DWORD_BIT_DIVISOR)
#define DWORD_SHIFT(bits)        ((bits) & DWORD_BIT_LEN_MASK)

#define MAX_BITS_PER_CELL        5
#define CELL_VALUE_OFFSET        1

typedef struct MemSegInd
{
    WORD32 startPos;
    WORD32 dstShift;
    WORD32 dstGap;
    WORD32 segMask;
}MemSegInd;

void InitMemSegCtrl(MemSegInd *memSegInd, WORD32 maxSegNum, WORD32 segLen)
{
    WORD32 segMask = ((1U << segLen) - 1);
    for(WORD32 segStart = 0; segStart < maxSegNum; ++segStart)
    {
        WORD32 startPos = segStart * segLen;
        WORD32 dstShift = DWORD_SHIFT(startPos);
        WORD32 dstGap = DWORD_MAX_BIT_NUM - dstShift;
        memSegInd[segStart].startPos = DWORD_INDEX(startPos);
        memSegInd[segStart].dstShift = dstShift;
        memSegInd[segStart].dstGap = dstGap;
        memSegInd[segStart].segMask = segMask;
    }
}
/****************************************************************************
 * SetMemSegValue:将内存分段保存,每次存储segLen
 * 参数:
 * pool:内存首地址
 * segStart:当前要设置的内存段的起始位置
 * segLen:从segStart开始,设置segLen bit内存值,不能大于等于32,否则设置没有意义
 * value:需要保存的值,范围1~2^segLen-1,保存时减去偏移量1.不在这个范围,保存异常
 ***************************************************************************/
void SetMemSegValue(WORD32 *addr, MemSegInd *memSegInd, WORD32 segStart, WORD32 segLen, WORD32 value)
{
    if(segLen >= DWORD_MAX_BIT_NUM) return;
    MemSegInd *seg = &memSegInd[segStart];
    WORD32 *dstPos  = addr + seg->startPos;

    WORD32 segValue = ((value - CELL_VALUE_OFFSET) & seg->segMask);
    *dstPos &= (~seg->segMask << seg->dstShift);   // 先清除对应段,防止反复写入不同值是由脏值
    *dstPos |= segValue << seg->dstShift;
    if(seg->dstGap != DWORD_MAX_BIT_NUM && seg->dstGap < segLen)
    {
        dstPos++;
        *dstPos &= (~seg->segMask >> seg->dstGap);  // 先清除对应段,防止反复写入不同值是由脏值
        *dstPos |= (segValue >> seg->dstGap);
    }
}

/****************************************************************************
 * GetMemSegValue:读取内存分段保存的值,每次存储segLen
 * 参数:
 * pool:内存首地址
 * segStart:当前要读取的内存段的起始位置
 * segLen:从segStart开始,读取segLen bit内存值,需要加上偏移量1
 ***************************************************************************/
WORD32 GetMemSegValue(WORD32 *addr, MemSegInd *memSegInd, WORD32 segStart, WORD32 segLen)
{
    if(segLen >= DWORD_MAX_BIT_NUM) return 0xFFFFFFFF;
    MemSegInd *seg = &memSegInd[segStart];
    WORD32 *dstPos  = addr + seg->startPos;

    WORD32 segValue = (*dstPos >> seg->dstShift) & seg->segMask;
    if(seg->dstGap != DWORD_MAX_BIT_NUM && seg->dstGap < segLen)
    {
        dstPos++;
        segValue = ((*dstPos << seg->dstGap) | segValue) & seg->segMask;
    }
    return (segValue + CELL_VALUE_OFFSET);
}

/**测试内存段的读写
 * */
void TestMemSegValue(void)
{
    // 1:测试一个32bit的内存可以存放16个2bit的值,测试读写正常
    WORD32 segLen = 2;// 2bit保存的值范围1~4,32bit可以存16个2bit的值,0不用保存
    WORD32 varPool = 0;
    MemSegInd varPoolMemSegInd[16] = {0};
    InitMemSegCtrl(varPoolMemSegInd, 16, segLen);
    for(WORD32 value = 1; value < 5; ++value)
    {
        for(WORD32 pos = 0; pos < 16; ++pos)
        {
            SetMemSegValue(&varPool, varPoolMemSegInd, pos, segLen, value);
            printf("varPool SegStartPos: %d Write Value: %d Read Value: %d\n", pos, value, GetMemSegValue(&varPool, varPoolMemSegInd, pos, segLen));
        }
    }
    // 2:测试一个10个32bit的内存可以存放64个5bit的值,测试读写正常
    WORD32 arrayPool[10] = {0};
    segLen = 5;// 5bit保存的值范围1~32,0不用保存
    MemSegInd arrayMemSegInd[64] = {0};
    InitMemSegCtrl(arrayMemSegInd, 64, segLen);
    for(WORD32 value = 1; value < 33; ++value)
    {
        for(WORD32 pos = 0; pos < 64; ++pos)
        {
            SetMemSegValue(arrayPool, arrayMemSegInd, pos, segLen, value);
            printf("arrayPool SegStartPos: %d Write Value: %d Read Value: %d\n", pos, value, GetMemSegValue(arrayPool, arrayMemSegInd, pos, segLen));
        }
    }
}


int main(void)
{
    setvbuf(stdout,NULL,_IONBF,0);
    TestMemSegValue();

    return EXIT_SUCCESS;
}

C语言实现(性能稍差)

(1)优点:没有额外内存开销

(2)缺点:重复计算,计算耗时

#include <stdio.h>
#include <stdlib.h>

typedef unsigned int        WORD32;
typedef unsigned char       UCHAR;

#define GET_TYPE_BIT_LEN(type)   (sizeof((type)) << 3)
#define DWORD_BIT_DIVISOR        5
#define DWORD_BIT_LEN_MASK       0x1F
#define DWORD_MAX_BIT_NUM        32

#define DWORD_INDEX(bits)        ((bits) >> DWORD_BIT_DIVISOR)
#define DWORD_SHIFT(bits)        ((bits) & DWORD_BIT_LEN_MASK)

#define MAX_BITS_PER_CELL        5
#define CELL_VALUE_OFFSET        1
/****************************************************************************
 * SetMemSegValue:将内存分段保存,每次存储segLen
 * 参数:
 * pool:内存首地址
 * segStart:当前要设置的内存段的起始位置
 * segLen:从segStart开始,设置segLen bit内存值,不能大于等于32,否则设置没有意义
 * value:需要保存的值,范围1~2^segLen-1,保存时减去偏移量1.不在这个范围,保存异常
 ***************************************************************************/
void SetMemSegValue(WORD32 *addr, WORD32 segStart, WORD32 segLen, WORD32 value)
{
    if(segLen >= DWORD_MAX_BIT_NUM) return;
    WORD32 startPos = segStart * segLen;
    WORD32 dstShift = DWORD_SHIFT(startPos);
    WORD32 dstGap = DWORD_MAX_BIT_NUM - dstShift;
    WORD32 *dstPos = addr + DWORD_INDEX(startPos);
    WORD32 segMask = ((1U << segLen) - 1);
    WORD32 segValue = ((value - CELL_VALUE_OFFSET) & segMask);

    *dstPos &= (~segMask << dstShift);   // 先清除对应段,防止反复写入不同值是由脏值
    *dstPos |= segValue << dstShift;
    if(dstGap != DWORD_MAX_BIT_NUM && dstGap < segLen)
    {
        dstPos++;
        *dstPos &= (~segMask >> dstGap);  // 先清除对应段,防止反复写入不同值是由脏值
        *dstPos |= (segValue >> dstGap);
    }
}

/****************************************************************************
 * GetMemSegValue:读取内存分段保存的值,每次存储segLen
 * 参数:
 * pool:内存首地址
 * segStart:当前要读取的内存段的起始位置
 * segLen:从segStart开始,读取segLen bit内存值,需要加上偏移量1
 ***************************************************************************/
WORD32 GetMemSegValue(WORD32 *addr, WORD32 segStart, WORD32 segLen)
{
    if(segLen >= DWORD_MAX_BIT_NUM) return 0xFFFFFFFF;
    WORD32 startPos = segStart * segLen;
    WORD32 dstShift = DWORD_SHIFT(startPos);
    WORD32 dstGap = DWORD_MAX_BIT_NUM - dstShift;
    WORD32 *dstPos = (addr + DWORD_INDEX(startPos));
    WORD32 segMask = ((1U << segLen) - 1);

    WORD32 segValue = (*dstPos >> dstShift) & segMask;
    if(dstGap != DWORD_MAX_BIT_NUM && dstGap < segLen)
    {
        dstPos++;
        segValue = ((*dstPos << dstGap) | segValue) & segMask;
    }
    return (segValue + CELL_VALUE_OFFSET);
}

/**测试内存段的读写
 * */
void TestMemSegValue(void)
{
    // 1:测试一个32bit的内存可以存放16个2bit的值,测试读写正常
    int segLen = 2;// 2bit保存的值范围1~4,32bit可以存16个2bit的值,0不用保存
    WORD32 varPool = 0;
    for(int value = 1; value < 5; ++value)
    {
        for(int pos = 0; pos < 16; ++pos)
        {
            SetMemSegValue(&varPool, pos, segLen, value);
            printf("varPool SegStartPos: %d Write Value: %d Read Value: %d\n", pos, value, GetMemSegValue(&varPool, pos, segLen));
        }
    }
    // 2:测试10个32bit的内存可以存放64个5bit的值,测试读写正常
    WORD32 arrayPool[10] = {0};
    segLen = 5;// 5bit保存的值范围1~32,0不用保存
    for(int value = 1; value < 33; ++value)
    {
        for(int pos = 0; pos < 64; ++pos)
        {
            SetMemSegValue(arrayPool, pos, segLen, value);
            printf("arrayPool SegStartPos: %d Write Value: %d Read Value: %d\n", pos, value, GetMemSegValue(arrayPool, pos, segLen));
        }
    }
}



int main(void)
{
    setvbuf(stdout,NULL,_IONBF,0);
    TestMemSegValue();
	return EXIT_SUCCESS;
}

总结:建议使用第一种实现, 性能更优。

注意:在ecplise下使用CDT开发C/C++程序中,使用debug调试时,到了printf 打印函数,在console窗口中并没有打印出信息来,停止后才会有输出。原因是:在debug调试时, eclipse 将输出的内容存放到了输出缓存区中,没有及时的输出到控制台。等到调试结束时,再将所有的信息一并打印出来。

解决方法:推荐使用:在程序开始时设置缓冲区类型,setvbuf函数在stdio.h文件声明。

setvbuf(stdout,NULL,_IONBF,0);

函数原型:

函数名: setvbuf

头文件:stdio.h
用 法: int setvbuf(FILE *stream, char *buf, int type, unsigned size);
type : 期望缓冲区的类型:
_IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
_IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
_IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
size : 缓冲区内字节的数量。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值