什么情况下使用alloca()
比声明固定大小数组分配在堆栈上的内存更好?
详情:
我们知道,alloca()
是一个有争议的函数。如果使用不当,它可能会导致堆栈溢出。但如果使用得当,它可以通过避免堆分配,在紧密循环中节省几个纳秒。在关于为什么 alloca
被认为是不好的这个问题中,一些顶级答案主张偶尔使用 alloca
。
另一种从堆栈分配内存的方法是简单地声明一个固定大小的数组。在Howard Hinnant的堆栈分配器的 arena
类中就有这种策略的示例。 (当然,那段代码是 C++ 的,但这个概念仍然适用于 C。)
使用 alloca
和使用固定大小的数组有哪些权衡之处?什么时候,如果有的话,一个明显优于另一个?这是否仅仅是一个性能问题,应该在每个单独的情况中进行实证测试(当性能是关键目标,已经确定了热点时)? 固定大小的数组更为悲观 - 它总是分配我们愿意在堆栈上分配的内存大小 - 但这是否是好事还是坏事尚不清楚。
为了尽可能清晰,这里有一个非常简单的示例,其中存在使用 alloca
或固定大小数组的理由:
#include <alloca.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
void foo_alloca(const size_t mem_needed) {
printf("foo_alloca(%zu)\n", mem_needed);
char* mem;
bool used_malloc = false;
if (mem_needed <= 100)
mem = alloca(mem_needed);
else {
mem = malloc(mem_needed);
used_malloc = true;
}
assert(mem_needed != 0);
// imagine we do something interesting with mem here
mem[0] = 'a';
mem[1] = 'b';
mem[2] = 'c';
mem[3] = '\0';
puts(mem);
if (used_malloc)
free(mem);
}
void foo_fixed(const size_t mem_needed) {
printf("foo_fixed(%zu)\n", mem_needed);
char* mem;
char stack_mem[100];
bool used_malloc = false;
if (mem_needed <= 100)
mem = stack_mem;
else {
mem = malloc(mem_needed);
used_malloc = true;
}
assert(mem_needed != 0);
// imagine we do something interesting with mem here
mem[0] = 'a';
mem[1] = 'b';
mem[2] = 'c';
mem[3] = '\0';
puts(mem);
if (used_malloc)
free(mem);
}
int main()
{
foo_alloca(30);
foo_fixed(30);
foo_alloca(120);
foo_fixed(120);
}
另一个与alloca
非常相似的选项是VLA。据我了解,从alloca
和VLA获得的内存基本上具有相同的行为,因此该问题也适用于VLA。如果这种理解是错误的,请提出来。
malloc
必须考虑以一种管理内存的方式,使其能够高效地释放和重新分配内存。在堆栈上,它只需将堆栈指针向后移动到需要的位置,然后就完成了。 - Rileyalloca
通常不需要进入内核模式。如果需要,它可能只需要扩展堆栈空间,这不会在每次调用时发生。但我不知道如何确定 glibc 函数是否进入内核模式。 - Praxeoliticstrace
运行简单测试后,似乎alloca
并不会进行系统调用。因此,它不应比固定数组慢太多。当内存耗尽时,alloca
不会给出任何警告,只是 UB(未定义行为),请参见这里。 - Riley