频繁调用malloc()和free()函数有多糟糕?

21

我正在发送一个文本文件 - 客户端和服务器将文本分成每个512字节的数据包,但有些数据包包含的文本大小不到最大值。在服务器端接收每个数据包时,我会调用malloc()重新构建一个字符串。这是一种糟糕的做法吗?是否更好保持一个可容纳最大长度的工作缓冲区并继续迭代,复制和覆盖其值?

好的@n.m.,这里是代码,这个if语句在由select()唤醒的for(;;)循环内部:

if(nbytes==2) {
            packet_size=unpack_short(short_buf);
            printf("packet size is %d\n",packet_size);
            receive_packet(i,packet_size,&buffer);
            printf("packet=%s\n",buffer);
            free(buffer);
}
//and here is receive_packet() function 
int receive_packet(int fd,int p_len,char **string) {
 *string = (char *)malloc(p_len-2); // 2 bytes for saving the length    
 char *i=*string;   
 int temp;
 int total=0;
 int remaining=p_len-2;
 while(remaining>0) {
     //printf("remaining=%d\n",remaining);
     temp = recv(fd,*string,remaining,0);
     total+=temp;
     remaining=(p_len-2)-total;
     (*string) += temp;
 }
 *string=i;
 return 0;
 }
7个回答

28
在您的示例中,函数已经包含一个系统调用,因此malloc/free的相关成本将几乎无法测量。在我的系统上,malloc/free的“往返”平均约为300个周期,而最便宜的系统调用(获取当前时间、pid等)至少需要2500个周期。预计recv的成本会轻松超过这个数值的10倍,这种情况下分配/释放内存的成本最多只占此操作总成本的大约1%。
当然,确切的时间会有所不同,但数量级的粗略估计在各个系统中应该是相对稳定的。我甚至不考虑在纯粹的用户空间函数中除去malloc/free作为一种优化方法。在那些应该没有失败情况的操作中,可能更有价值的是去掉动态分配,因为这样可以通过不必担心malloc失败来简化强化代码。

2
我正在计时 free(malloc(1));(在Linux/glibc/i686上,使用rdtsc测量),当它不需要从操作系统映射新内存,而是重用以前释放的现有内存。这几乎总是最常见的情况。唯一需要担心从操作系统获取新内存所需时间的情况是实时编程,其中您关心任何操作的最坏情况延迟,而不是程序的整体运行时间。 - R.. GitHub STOP HELPING ICE
3
使用一个包含10-100个初始化为NULL的数组,并随机分配各种大小的内存块进行malloc/free操作,将是一个更好的测试。 - Zan Lynx
1
好的。有趣的东西。这是我为16个随机插槽分配高达16384字节找到的结果:tsc平均循环=247,最长循环的tsc = 833292。 - Zan Lynx
1
对于每个512个插槽,每个插槽最多163840字节的:tsc平均循环= 408 最长循环的tsc = 294350 - Zan Lynx
@mathematrucker: “easier on the hardware”是什么意思?你提出的两个选择都不能代表实际合理的选择,实际上合理的选择应该是每次使用malloc/free分配和释放内存,或要求调用者提供已经分配好的工作空间(而不是全局的)。 - R.. GitHub STOP HELPING ICE
显示剩余4条评论

7
调用malloc和free会带来一定的开销。在分配内存块时,需要从堆中分配出一个内存块并标记为已用;释放时则相反。由于不知道您使用的是什么操作系统或编译器,这可能是在C库中实现还是在操作系统的内存管理级别上实现。如果您频繁地进行malloc和free,可能会导致堆严重碎片化,使您可能没有足够的连续空闲内存来在其他位置进行malloc。如果您只能分配一个缓冲区并重复使用它,通常会更快,并且更少碰到堆碎片化的危险。

5
如果他一遍又一遍地分配和释放一个固定大小的块,几乎肯定不会出现内存碎片化的问题。(这并不意味着没有任何开销...但在这种情况下,造成问题的可能不是内存碎片化。) - Nemo
假设程序中没有其他东西从堆中分配内存,并且它是单线程的,我同意。很可能每次返回的是相同的内存块。然而,我们不知道程序中还有什么其他东西从堆中分配,或者这个特定的堆管理器是如何实现的。 - user957902
考虑到屏幕保护程序的原始目的,如果我们在谈论一个长时间运行的程序,那么似乎在一个被调用了无数次的函数内部分配和释放一个数组比全局分配和释放一次更容易对硬件造成影响。坦白地说,我不知道这是否是一个有效的考虑因素...我知道存在着恶意硬件代码,但我怀疑全局分配并不属于这个范畴 :) - mathematrucker

4
我发现malloc,realloc和free的开销很大。如果可以避免使用malloc,最好重复使用已经拥有的内存。
编辑:
看起来我对malloc的开销评估是错误的。在Linux上使用GNU C库版本2.14进行一些定时测试后,可知对于一个循环100,000次并且随机分配大小从1到163840字节的512个插槽进行分配和释放的测试:
tsc average loop = 408
tsc of longest loop = 294350

所以,在紧密的内部循环中浪费408个周期进行mallocnew将是一件愚蠢的事情。除此之外,不必担心它。


4

一般来说,Malloc的代价相对较低。只有在生成系统调用以获取更多堆空间时才会昂贵。例如,在类UNIX系统中,它最终会生成一个sbrk调用,这将是昂贵的。如果您反复malloc和free相同大小的内存,它将非常快。例如,请考虑以下小测试程序:

#include <stdlib.h>


int main()
{
  int i=0;
  int *ptr;

  for(int i=0; i<1e6; i++) {
    ptr = malloc(1024*sizeof(int));
    free(ptr);
  }
}

它分配了1024个整数并释放它们,这样做一百万次。在我的比较普通的Chromebook转Linux机器上运行时,我得到的时间如下:

time ./test

real    0m0.125s
user    0m0.122s
sys     0m0.003s

现在,如果我注释掉循环中的malloc和free部分,就会得到这些时间:
time ./test

real    0m0.009s
user    0m0.005s
sys 0m0.005s

所以你可以看到,malloc和free确实有开销,虽然我认为仅仅是比什么都不做多了十多倍的开销并不算太大。

如果它能够一直重复使用堆的同一块区域(就像这里的情况),那么速度会特别快。当然,如果我不断地分配和增长程序,那么它将需要更多的时间,因为这会导致一些系统调用。

当然,具体情况取决于操作系统、编译器和stdlib实现。


2
多次调用malloc/free可能会增加进程使用的内存(即使没有泄漏),如果传递给malloc的大小是可变的,这已经由这个问题证明了:链接1。因此,单个缓冲区方法可能是最好的。

1

只有测试才能说明问题。在使用C语言编程时,我通常会避免使用malloc函数,因为如果不小心创建了内存泄漏,修复起来可能会非常困难。


1

衡量这两种解决方案的性能,可以通过分析或测量吞吐量来实现。无法确定任何事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接