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

被折叠的 条评论
为什么被折叠?



