当创建大型缓冲区时,使用malloc分配缓冲区是否比使用静态分配的缓冲区更快?

3
我将尝试创建一个像这样的数组:
char tmp[4][32768];
//code

这是一个函数,我发现将此数组设为全局数组会增加程序的速度(不幸的是,当遇到某些数据时该函数会递归调用自身,因此不能保持全局)。我想让这个函数更快,但我读到过malloc可能会相当慢。所以是让它保持原样还是做如下修改更快呢?

char** tmp;
tmp = malloc(4 * sizeof(char*));
tmp[0] = malloc(32768);
tmp[1] = malloc(32768);
tmp[2] = malloc(32768);
tmp[3] = malloc(32768);
//code
free(tmp[0]);
free(tmp[1]);
free(tmp[2]);
free(tmp[3]);
free(tmp);

2
不可判定。您需要进行基准测试。 - Antti Haapala -- Слава Україні
3
tmp = malloc(4); 分配的内存不足,需要改为 tmp = malloc(4 * sizeof (char *)); - mediocrevegetable1
@mediocrevegetable1 谢谢,我忘记了,哈哈(我已经更新了帖子) - PQCraft
如果你正在处理关键代码,而且你的函数不是递归的,那么你可以在堆中预分配一个大的线程本地缓冲区来减轻执行malloc操作的成本(但不要忘记释放它)。 - Jérôme Richard
你的递归有限制吗?如果有,那么使用堆栈分配就可以轻松解决。如果没有限制,你可以通过动态分配全局数组来管理它们,需要时分配新的数组,并在退出时释放它们。最大分配次数将取决于你的递归次数。 - Vlad Feinstein
2个回答

12

问题不在于速度快慢,而在于是否会导致堆栈溢出。

你创建了一个大小为128K的数组,这对于本地变量来说相当大,本地变量通常驻留在堆栈上。如果递归调用该函数,则每次调用都会在堆栈上增加128K。只要递归调用几次就足以导致堆栈溢出,这可能会导致崩溃。

动态分配内存几乎是您唯一的选择。但您可以将其减少到单个分配。

char (*tmp)[32768] = malloc(4 * sizeof *tmp);

这种分配方式有效是因为一个类型为 char [4][32768] 的二维数组会衰变为一个类型为 char (*)[32768] 的指针,与上述的 tmp 类型匹配。由 malloc 返回的内存足够存放 4 个类型为 char[32768] 的对象。这块内存是连续的,不像对每个子数组进行 4 次单独的 malloc 调用。


2
由于 sizeof(char) 被保证为 1,我相信 malloc(4 * 32768) 等同于 malloc(4 * sizeof(char [32768])) - mediocrevegetable1
3
@PQCraft,这似乎是使用“goto”的一个很好的案例。 - dbush
3
这将使你的数组不连续,可能会影响访问速度。答案中的方法使用单个连续的内存块,就像2D数组一样。 - dbush
2
@MichaelDorgan 这不是一个指针数组,而是一个指向32768个char数组的指针,这得益于()和运算符优先级。希望这能澄清问题(刚刚注意到答案已经编辑过了,那样解释更好)。 - mediocrevegetable1
2
@JérômeRichard sizeof的操作数只有在它是VLA时才会被评估。它仅仅被观察其类型,并在编译时计算。 - dbush
显示剩余10条评论

1
所有条件相等的情况下,通过malloc(或callocrealloc)分配内存将比使用auto(本地)数组更慢且更费力,而使用static数组会比使用auto数组更慢。
然而,通常情况下并非如此。正如您发现的那样,在处理递归时(或者当您需要保留多个状态时,就像任何尝试使用strtok对像"a=b&c=d&e=f"这样的字符串进行标记化的人都知道的那样),static数组效果不佳。128K的auto数组有点大,如果函数是递归的,那么这可能是一个问题,因为栈空间通常对于单个进程来说是有限的。
这是动态内存的理想用例,它通常不像堆栈上的内存那样受限制。权衡之处在于,它将比其他替代方案更慢,并且需要您付出更多的努力。

具体减速程度取决于您如何分配和使用它 - 将其分配为单个连续块只需要一个malloc(和相应的free)调用,应该更有利于局部性:

char (*tmp)[32768] = malloc( 4 * sizeof *tmp );
if ( tmp )
{
  for ( size_t i = 0; i < 4; i++ )
    strcpy( tmp[i], some_string ); // or however you intend to use tmp;
  ...
  free( tmp );
}

但如果你的堆碎片化严重,你可能无法将其作为单个连续块分配,并且必须分开进行,使用多个malloc(和free)调用:

char **tmp = malloc( 4 * sizeof *tmp );
if ( tmp )
{
  for ( size_t i = 0; i < 4; i++ )
  {
    tmp[i] = malloc( sizeof *tmp[i] * 32768 );
    if ( tmp[i] )
    {
      strcpy( tmp[i], some_string );
    }
  }
  ...
  for ( size_t i = 0; i < 4; i++ )
    free( tmp[i] );
  free( tmp );
}

唯一确定哪种方法更快、更适合您的目的,是编写多个版本并测量它们的性能。
话虽如此...
如果代码执行错误、给出错误答案、在第一次接收到糟糕输入时崩溃、暴露敏感数据、提供恶意软件入口或无法修改和维护,则代码速度再快也没有用。首先考虑正确性和可维护性,而不考虑原始执行速度,然后考虑上述所有问题。除非您有硬性性能要求(“此操作必须在X毫秒内完成”),否则不必担心速度。首先确保正确性,然后如果您不满意,可以随时调整性能。
此外,根据您的其余代码所做的工作,分配和管理内存的不同方法之间的差异可能微不足道——如果您的代码大部分时间都在等待某人输入数据,则使用malloc而不是一个auto数组花费的额外时间几乎没有影响。另一方面,如果您在紧密循环中调用此代码数千次,则这些额外的时钟将累加成显著的差异。
根据分析和测量做出决策,不要凭猜测。

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