【问题标题】:Avoiding malloc/free Overhead in structure allocations in C避免 C 中结构分配中的 malloc/free 开销
【发布时间】:2018-02-13 09:56:21
【问题描述】:

我正在阅读和尝试这本书中的指针,

http://shop.oreilly.com/product/0636920028000.do

在本书第 6 章的 Avoiding malloc/free Overhead 标题下 作者建议在进行大量结构内存分配/释放时如何避免 malloc/free 开销。

下面是他写函数的方式,

#define LIST_SIZE 10
Person *list[LIST_SIZE];

void initializeList() 
{
    int i=0;
    for(i=0; i<LIST_SIZE; i++) 
    {
        list[i] = NULL;
    }

}

Person *getPerson() 
{
    int i=0;
    for(i=0; i<LIST_SIZE; i++) 
    {

        if(list[i] != NULL) 
        {
            Person *ptr = list[i];
            list[i] = NULL;
            return ptr;
        }
    }
    Person *person = (Person*)malloc(sizeof(Person));
    return person;
}

void deallocatePerson(Person *person) 
{
    free(person->firstName);
    free(person->lastName);
    free(person->title);
}

Person *returnPerson(Person *person)
{
    int i=0;
    for(i=0; i<LIST_SIZE; i++) 
    {
    if(list[i] == NULL) 
        {
            list[i] = person;
            return person;
        }
    }
    deallocatePerson(person);
    free(person);
    return NULL;
}

我从他的代码中了解到,他创建了一个内存池数组,指向 struct person 类型,然后用 NULL 初始化每个数组元素。

接下来我们将使用 getPerson 函数从池中获取内存。这个函数检查 !=NULL 我认为每次都会失败。所以它还是一样的,因为做 malloc 和内存不会随时从池中分配。

  1. 我的理解正确吗?
  2. 这是处理开销的方法吗?
  3. 正确的方法应该是什么?任何来源/链接将不胜感激。

【问题讨论】:

  • 在大多数(但不是全部)情况下,malloc 足够快(例如,在我的 PC 上不到一微秒)。我建议在进行此类优化之前进行基准测试。
  • @BasileStarynkevitch 如何进行基准测试?
  • 这是一个非常不同的问题,并且是特定于操作系统和特定于编译器的。也许您可以在代码中使用clock。在 Linux 上,您还可以使用 time(1)perf(1)gprof(1) 等……另请参阅 time(7)
  • 但是这些功能也会增加开销。不是吗?

标签: c


【解决方案1】:

接下来我们将使用 getPerson 函数从池中获取内存。这个函数检查!=NULL,我认为每次都会失败。

只要您继续反复调用getPerson,每次检查都会失败。但是,如果您混合使用getPersonreturnPerson,则某些NULL 检查会成功,因为returnPerson 会将非NULL 值放入数组中。

这一观察是理解该方法的关键:该数组用作struct Person 块的小型临时存储,这些块已分配有malloc,但不再使用。您的代码不会再次调用malloc,而是从这个特殊列表中获取一个可用块(如果有可用块)。

在您进行数千次分配但在任何给定时间从未保持超过LIST_SIZE 的活动对象的情况下,malloc 调用的数量限制为LIST_SIZE

这是处理开销的方法吗?

这是使用后备列表的一种变体,这是一种非常重要的优化技术,Microsoft 创建了an API for its use in driver code。一种更简单的方法是使用Person *list[LIST_SIZE] 作为释放块的堆栈,即使用最后一个释放块的索引并且没有循环。

另一种方法是设置这些块的链表,重用块本身的内存来存储next 指针。不过,这种技术对于介绍性的嘘声来说可能太复杂了。

【讨论】:

  • 所以这种方法的重点是在解除分配时保存 10 个已分配的块,从而在进一步的时间点减少 10 次内存分配的开销。
  • @nikhilchaubey 为什么,只要您在任何给定时间从未有超过十个活动项目,这种技术就可以减少数百万次分配的开销。例如,你得到十个项目,然后返回十个项目,你重复这个“得到十个/返回十个”循环一百万次。你总共有十个分配,而不是一千万个分配和一千万个释放;所有其他“获取”都来自list
  • 确实如此。一般来说,这种技术只节省了 10 次分配。在 1000 个中,将保存 10 个,其余 990 个将进行开销分配/取消分配。
【解决方案2】:

首先,您的作者在这里指的开销是什么?对于动态内存分配,我们调用malloc 分配内存,调用free 释放内存。在此过程中,操作系统也需要从堆中搜索可用内存并分配相同的内存。为了避免这种开销,他只是建议一开始当您的应用程序加载时,如果您知道可能动态内存分配给struct 的频率,您可以提前保留一个内存池,这将减少分配和释放显着的内存开销。这在一定程度上是正确的,如果您的服务器已经在运行大量应用程序并且处理器非常繁忙,您可以采用这种方法。但也有缺点。在这种情况下,您已经提前从堆中保留了一个内存池。如果使用不当,会导致内存管理不善。

【讨论】:

    【解决方案3】:

    我认为这个例子的重点可能是Person 对象持有指向额外内存的指针。我们可以从deallocatePerson函数中看到结构体内部有3个指向字符串的指针:

    void deallocatePerson(Person *person) 
    {
        free(person->firstName);
        free(person->lastName);
        free(person->title);
    }
    

    这意味着要构造一个完整的Person,您需要多次调用malloc(1 次用于结构本身,3 次用于字符串)。

    因此,通过保存一个完整的结构(包括其字符串),一个getPerson 调用将替换四个malloc 的调用。这可能会节省一些执行时间。

    否则,如果malloc/free 内部包含一个类似的数组或最近使用的准备回收的内存块的链接列表,我不会感到惊讶。如果您刚刚free 找到了正确大小的内存块,则对malloc 的新调用可能会非常快速找到该块。

    如果Person 是一个简单的结构,没有指向额外存储的指针,则本地缓存 不太可能提高性能(但可能通过执行线性搜索)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-22
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多