如何优化纯C静态二维数组

cphj 2009-11-25 01:08:24
1. 问题背景
纯C代码使用静态二维数组管理核心数据,例如:
typedef struct Data_s
{
char valid; // 表示该块数据已经使用
char id;
//...
//...
} Data;
Data data[MAX_A][MAX_B];

MAX_A和MAX_B都小于50,但不能修改(因为是业务规格)

程序运行时,根据外部动态请求把单个数据元素写到data数组中,如:
AddData(Data * element, int a, int id);
把单个数据写到data[a][?]中,?是未使用(空闲)的元素,id是数据元素的唯一标号(在同一行内唯一,在不同行之间不唯一),id的范围0-255

访问数据时,按下标a加上id搜索,如:
ReadDataByID(Data * element, int a, char id);
现有代码是用for循环搜索id,效率为O(n)

2. 目标
因为开这个静态二维数组导致内存基本占满(非PC应用),但实际数组中有大量空闲元素,所以希望提高内存的使用率
同时,希望在整体内存使用减少的条件下,用部分内存来换取访问数据的速度提升


请问大家有什么想法,或者思路?

我的想法在下面,如果有错误,也尽管提

谢谢!
...全文
712 43 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
43 条回复
切换为时间正序
请发表友善的回复…
发表回复
qingyaoli 2009-11-25
  • 打赏
  • 举报
回复
mark
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 40 楼 whg01 的回复:]
引用 13 楼 akirya 的回复:
一般的平衡二叉树就可以了。
glib 有现成的实现 GTree

推荐使用平衡二叉树,插入、删除、查找都有很低的复杂度。
用下标作为排序的依据。
[/Quote]

嗯,本来也想用平衡二叉树,后来发现因为id的范围在0-255之间,很小,以后也很难会变(需要改标准),就倾向于直接用静态二维指针数组挂数据块了

具体方案,总结在28、35、37、41
有空的话,给把把关。。
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 38 楼 pf_ma 的回复:]
楼主好敬业阿,还在加班,我已经躺在床上了,:)

==================================================================

如果要提高到“碎片整理”的话,是不是我们考虑的太复杂了?

LPDATA Datas[MAX_A][256]; // 数据检索表
直接挂DATA反而说不定成了最优解[/Quote]
呵呵,已经下班了,又上来看看

项目上还是需要多报几个候选方案,否则只有一个也很难让所有人接受,多报几个,最后大家一般都会选个中等方案

你建议直接挂DATA和25楼的想法一致,从代码改动量上看,估计最后被选中的可能性很大

现在看来已经有几个成型的方案
1. LPDATA Datas[MAX_A][256];
二维指针数组,分配data时直接挂,释放时直接删了置0

2. 引入block机制,多加2个链表,以便减少内存碎片
性能比1好,但算法比1复杂多了

3. 对二维指针数组,引入hash把id下标[0,255]映射到[0,MAX_B](或者略宽)的范围内,以便减少一些指针开销
可以在1或者2的基础上附加,减少少量指针开销,但是要引入hash算法
whg01 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 akirya 的回复:]
一般的平衡二叉树就可以了。
glib 有现成的实现 GTree
[/Quote]
推荐使用平衡二叉树,插入、删除、查找都有很低的复杂度。
用下标作为排序的依据。
晴天1999 2009-11-25
  • 打赏
  • 举报
回复
新手哈!多多指教哟!
pf_ma 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 35 楼 cphj 的回复:]

嗯,目前汇总大家讨论之后的方案,和你的想法基本类似,区别是只记录一个空闲data link
一旦空闲data节点用完了,就申请一个block,然后把block拆成data节点挂在空闲data link上

另外,还补充讨论了一下,如何释放block的问题
如果空闲链表超过50个data节点,则整理一下碎片(因为空闲节点有可能来自各个block上删除的data节点),集中释放掉一个block。需要维护一个已分配链表,以便每次整理碎片时,找到最后那个已分配的block,把它和空闲链表中的50个节点对换之后释放。

到此为止,汇总方案为:
共需要一个已分配block link链表(以便释放时整理碎片)
一个空闲data link链表(以便快速分配空闲节点)
一个二维索引指针表(以便快速访问节点)

你看看是否还能优化??

[/Quote]

楼主好敬业阿,还在加班,我已经躺在床上了,:)

==================================================================

如果要提高到“碎片整理”的话,是不是我们考虑的太复杂了?

LPDATA Datas[MAX_A][256]; // 数据检索表
直接挂DATA反而说不定成了最优解


cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 34 楼 cattycat 的回复:]
使用链表维护整个数据节点,将每个节点的地址hash和其对应的索引存到一个map里,这样通过索引直接取到节点地址。
感觉map也会增加空间,当然map是动态增加的,不会开始就MAX*MAX大小。
你第四种方法也MAX个索引,每个有256个表项空间需求也很大啊。
[/Quote]

你提出的hash对我有启发,见后面分析

你提出的map方法,就等价于我在2楼给出的方案3.2吧?方案3.2是把节点指针和对应索引id直接map起来

最终满配置肯定都会达到MAX*MAX,好在目前的目标是优化使用率,也就是说内存使用的最差情况是一样的,这个没办法改变,但最好情况是可以优化的
就像很多算法,它们不能提高最差条件下的性能,但能提高平均性能,或者能提高最好条件下的性能也是可贵的

方案3.4,使用少量空间来换访问速度,否则原先遍历找id对应的数据节点为O(n),虽然n并不大,但在实时系统里面还是很珍贵的
如果用方案3.2,增加MAX_A*MAX_B个指针,换取访问速度为O(logn)
方案3.4,增加MAX_A*256个指针,换取访问速度为O(1)
MAX_A*256个指针,大小为4*MAX_A*256,基本只相当于1-2个data节点的大小

现在看起来,如果继续追求优化,对id(0-256)做hash,映射到0-MAX_B的范围里面,则也可以只增加MAX_A*MAX_B个指针,而获得O(1)的数据访问速度
这样一来,需要完美hash函数的支持,否则数据多了hash冲突会很严重,或者要适当放宽0-MAX_B的范围
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 31 楼 z569362161 的回复:]
3.4挺好的
[/Quote]

现在的汇总方案已经在3.4原方案上增加了很多改进,敬请关注。。
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 30 楼 pf_ma 的回复:]
那就改成2个层次的双向链表(使用双向链表,主要是在删除时,不需要遍历链表了);
一个(全局的)用来纪录有空位的DATABLOCK
一个(DATABLOCK中)用来纪录有空位的DATA

typedefstruct tag_Data
{
LPDATABLOCK lpDataBlock;// Data所属的DataBlock的地址;// DataBlock分配完成,初始化数据时就可以填充完成 LPDATA lpNextFreeData;
LPDATA lpPrevFreeData;
BYTE btID;//...//...}DATA, FAR* LPDATA;

typedefstruct tag_DataBlock
{byte btCount;// 未使用的DATA的个数
LPDATABLOCK lpNextFreeDataBlock;
LPDATABLOCK lpPrevFreeDataBlock;

LPDATA lpHeadFreeData;
LPDATA lpTailFreeData;

DATA Datas[MAX_DATA];
}DATABLOCK, FAR* LPDATABLOCK;

LPDATABLOCK lpHeadFreeDataBlock;
LPDATABLOCK lpTailFreeDataBlock;

链表维护本身也是一种开销,你的MAX_A和MAX_B本身并不大,所以MAX_DATA + MAX_DATABLOCK因该也不太大,这个不知能优化多少[/Quote]

嗯,目前汇总大家讨论之后的方案,和你的想法基本类似,区别是只记录一个空闲data link
一旦空闲data节点用完了,就申请一个block,然后把block拆成data节点挂在空闲data link上

另外,还补充讨论了一下,如何释放block的问题
如果空闲链表超过50个data节点,则整理一下碎片(因为空闲节点有可能来自各个block上删除的data节点),集中释放掉一个block。需要维护一个已分配链表,以便每次整理碎片时,找到最后那个已分配的block,把它和空闲链表中的50个节点对换之后释放。

到此为止,汇总方案为:
共需要一个已分配block link链表(以便释放时整理碎片)
一个空闲data link链表(以便快速分配空闲节点)
一个二维索引指针表(以便快速访问节点)


你看看是否还能优化??
cattycat 2009-11-25
  • 打赏
  • 举报
回复
使用链表维护整个数据节点,将每个节点的地址hash和其对应的索引存到一个map里,这样通过索引直接取到节点地址。
感觉map也会增加空间,当然map是动态增加的,不会开始就MAX*MAX大小。
你第四种方法也MAX个索引,每个有256个表项空间需求也很大啊。
东大坡居士 2009-11-25
  • 打赏
  • 举报
回复
我是来学习的...
pf_ma 2009-11-25
  • 打赏
  • 举报
回复
LPDATA lpNextFreeData;
...

LPDATABLOCK lpHeadFreeDataBlock;
...

貌似使用指针内存浪费的太多了,改成数组的下标算了

Byte btNextFreeDataIdx;
...

z569362161 2009-11-25
  • 打赏
  • 举报
回复
3.4挺好的
pf_ma 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 23 楼 cphj 的回复:]

很好的思路!的确,系统中的数据分配、释放还是很频繁的

进一步讨论一下
使用block link,删除中间节点数据时,只是标记该节点空闲。那么下次分配的时候,就要遍历整个已分配的block link,如果block中未使用的节点的个数>0,则再遍历该block,找到标记为空闲的节点,使用它。这样分配代价是不是太大了?

感觉,把你的方案和14楼的组合在一起,可以更好,如下:
使用block link分配内存,删除中间节点数据时,把最后一个节点搬到删除处,这样添加节点永远都在尾部,不需要遍历(整体上维护一个尾block指针,和尾block上的空闲DATA节点指针)

或者还可以再利用一下索引表,继续优化方案??
[/Quote]

那就改成2个层次的双向链表(使用双向链表,主要是在删除时,不需要遍历链表了);
一个(全局的)用来纪录有空位的DATABLOCK
一个(DATABLOCK中)用来纪录有空位的DATA



typedef struct tag_Data
{
LPDATABLOCK lpDataBlock; // Data所属的DataBlock的地址;
// DataBlock分配完成,初始化数据时就可以填充完成
LPDATA lpNextFreeData;
LPDATA lpPrevFreeData;
BYTE btID;
//...
//...
}DATA, FAR * LPDATA;

typedef struct tag_DataBlock
{
byte btCount; // 未使用的DATA的个数

LPDATABLOCK lpNextFreeDataBlock;
LPDATABLOCK lpPrevFreeDataBlock;

LPDATA lpHeadFreeData;
LPDATA lpTailFreeData;

DATA Datas[MAX_DATA];
}DATABLOCK, FAR * LPDATABLOCK;

LPDATABLOCK lpHeadFreeDataBlock;
LPDATABLOCK lpTailFreeDataBlock;



链表维护本身也是一种开销,你的MAX_A和MAX_B本身并不大,所以MAX_DATA + MAX_DATABLOCK因该也不太大,这个不知能优化多少

cphj 2009-11-25
  • 打赏
  • 举报
回复
谢谢大家的热心帮助,我给帖子加了100分(等级不够,只能这么多了)

请大家继续提思路、意见。。
cphj 2009-11-25
  • 打赏
  • 举报
回复
现阶段总结如下:

2楼、12楼给出了3.4号链表+索引表方案描述

7楼、17楼给出了block link链表的改进

14楼提出了改进link上删除节点时,避免内存空洞的思路

23楼总结了目前为止block link+改进link内存空洞+索引表方案

25楼的思路我理解也是索引表,但是已分配的Data节点可以直接挂索引表项上,不需要用链表串起来

如果加上内存池管理,即是把空闲节点用链表管理起来
这样删除Data节点时,甚至不需要硬复制尾节点,直接把索引表项指针置0,然后把Data节点挂在空闲链表上
而空闲链表用完了就多申请一个block(如50个Data)
如果空闲链表超过50个Data,则整理一下碎片(因为空闲节点有可能来自各个block上删除的Data节点),集中释放掉一个block。这样看来还是需要维护一个已分配链表,以便每次整理碎片时,找到最后那个已分配的block,把它和空闲链表中的50个节点对换。


到此为止,总结方案:

需要一个已分配block link链表,一个空闲data link链表,一个索引指针表
内存空洞在释放block的时候集中整理,而不是在删除每个data节点的时候与尾节点对调


请大家继续分析改进。。。
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 25 楼 cangyingzhijia 的回复:]
要是Data较大的话可以:
把Data data[MAX_A][MAX_B];
改成
Data *data[MAX_A * 255];  *data中的每个元素存放Data地址,Data通过malloc、realloc分配,当然要是闲着分配不够快的话可以用我上面说的分配方法进行内存池管理
*data[MAX_A * 255]占不了多少内存的,而且存取都是o(1)的

下面是操作的实现:
int AddData(Data * element, int a,id);
//code:data[a*255 + id] = element;
ReadDataByID(Data * element, int a, char id);
// element = code:data[a*255 + id] ;

[/Quote]

你的思路又推动了方案的改进!
分析见下
qcl_517 2009-11-25
  • 打赏
  • 举报
回复
好好学习。。。
苍蝇①号 2009-11-25
  • 打赏
  • 举报
回复
要是Data较大的话可以:
把Data data[MAX_A][MAX_B];
改成
Data *data[MAX_A * 255]; *data中的每个元素存放Data地址,Data通过malloc、realloc分配,当然要是闲着分配不够快的话可以用我上面说的分配方法进行内存池管理
*data[MAX_A * 255]占不了多少内存的,而且存取都是o(1)的

下面是操作的实现:
int AddData(Data * element, int a,id);
//code:data[a*255 + id] = element;
ReadDataByID(Data * element, int a, char id);
// element = code:data[a*255 + id] ;
cphj 2009-11-25
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 pady_pady 的回复:]
一维数组比二维的快
[/Quote]

差点忽略了。。。请问具体方案?如何改成一维同时提高内存使用率?
加载更多回复(23)

70,020

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧