C中的内存池-内存管理
我正在用C编写一个跨平台共享库。该库的工作流程如下:C中的内存池-内存管理,c,memory-management,shared-libraries,pool,C,Memory Management,Shared Libraries,Pool,我正在用C编写一个跨平台共享库。该库的工作流程如下: lib *handle = lib_init(); result = lib_do_work(handle); lib_destroy(handle); 通常,用户会在应用程序启动时初始化它,并在应用程序结束时关闭它lib\u do\u work()通常在一秒钟内被调用多次。因此,为了避免每次调用的内存分配和释放,我使用了一种池机制。这样,我要求池返回我需要的结构实例。池将返回一个未使用的实例,如果没有可用的实例,则创建一个新实例。这个新实
lib *handle = lib_init();
result = lib_do_work(handle);
lib_destroy(handle);
通常,用户会在应用程序启动时初始化它,并在应用程序结束时关闭它lib\u do\u work()
通常在一秒钟内被调用多次。因此,为了避免每次调用的内存分配和释放,我使用了一种池机制。这样,我要求池返回我需要的结构实例。池将返回一个未使用的实例,如果没有可用的实例,则创建一个新实例。这个新实例也将添加到池中,以便下次可以使用它
对我的库的任何API调用都以函数调用reset\u pool()
开始,该函数使池中的所有元素再次可用。此池作为lib\u destroy()
调用的一部分被销毁。在我的测试中,我观察到有时我的池会得到100000多个结构实例实例
我想知道这是处理记忆的好方法吗?任何帮助都会很好。正如评论中已经指出的那样,只有分析才能告诉您分配和解除分配是否是应用程序中的瓶颈。此外,如果您的系统始终只分配和释放相同大小的对象,那么默认实现可能会执行得相当好 通常,池通过预分配块或元素来提供分配优化。块被分割成单独的元素以满足单独的分配请求。当池耗尽时,将分配一个新块。这是一种分配优化,因为更少地调用库分配器更便宜 池分配器还可以帮助减少碎片。如果应用程序分配和解除分配不同大小的对象,并具有不同的生命周期,那么碎片化的可能性就会增加(默认分配器中的合并代码必须做更多的工作)。如果为每个不同大小的对象创建了池分配器,并且每个池块的大小相同,那么这将有效地消除碎片 (正如Felice指出的,还有另一种池,它预先为应用程序分配固定数量的内存,以确保应用程序使用的内存不会超过配置的内存。) 释放时,可以将单个元素放置到自由列表中。但是您的
reset_pool
实现只需遍历块,释放每个块,然后分配一个新块
下面的内容有点过于简单。它只涉及一种元素。池大小需要调整为适合您的应用程序的合理大小。假设数据结构如下:
typedef struct element {
struct element *next;
/* ... */
} element;
typedef struct pool_block {
struct pool_block *next;
struct element block[POOL_SIZE];
} pool_block;
typedef struct element_pool {
struct pool_block *pools;
struct element *freelist;
int i;
} element_pool;
然后,API看起来像:
void pool_init (element_pool *p) { /* ... */ }
element * pool_alloc (element_pool *p) {
element *e = p->freelist;
if (e) p->freelist = e->next;
else do {
if (p->i < POOL_SIZE) {
e = &p->pools->block[p->i];
p->i += 1;
} else {
pool_block *b = pool_block_create();
b->next = p->pools;
p->pools = b;
p->i = 0;
}
} while (e == 0);
return e;
}
element * pool_dealloc (element_pool *p, element *e) {
e->next = p->freelist;
p->freelist = e;
}
void pool_reset (element_pool *p) {
pool_block *b;
while ((b = p->pools)) {
p->pools = b->next;
pool_block_destroy(b);
}
pool_init(p);
}
void pool_init(元素_pool*p){/*…*/}
元素*pool\u alloc(元素*pool*p){
元素*e=p->自由列表;
如果(e)p->freelist=e->next;
否则会{
如果(p->i<池大小){
e=&p->pools->block[p->i];
p->i+=1;
}否则{
pool_block*b=pool_block_create();
b->next=p->pool;
p->pools=b;
p->i=0;
}
}而(e==0);
返回e;
}
元素*pool\u dealloc(元素*pool*p,元素*e){
e->next=p->自由列表;
p->freelist=e;
}
无效池重置(元素池*p){
*b座泳池;
而((b=p->pool)){
p->pools=b->next;
池、块、破坏(b);
}
池_init(p);
}
我不知道您当前的体系结构是否过于复杂,但通常情况下,当所有池实例都很忙时,池会限制池实例和队列请求的数量。过早优化是万恶之源。您是否测量到每次调用的分配/解除分配都会降低应用程序的速度?我不确定这是否是过早的优化。我这样做的唯一原因是,1-我不需要每次都做分配。2-我不必到处管理内存。我所要做的就是在每个API方法的开头重置_池。谢谢。我有一个类似的实现,但没有多个池。每个结构都有一个动态增长的池。也就是说,structa将有一个池,structb将有另一个池。我想知道像您这样做有什么好处吗?同样在我的池实现中,我最初只分配一个元素。然后为每个调用增加2倍。我的重置池只会将索引恢复为0。@Appu:我不完全理解你的池是如何工作的,所以我无法将你的实现与我建议的实现进行比较。如果您可以在关于分配、取消分配和重置函数的问题中添加一点代码,我可以评论一下它们在多大程度上满足了您的目标。感谢你没有说清楚。您可以在这里看到我的池实现。