我正在开发一个软实时事件处理系统。我希望尽可能减少代码中具有非确定性时序的调用次数。我需要构造一个由字符串,数字,时间戳和GUID组成的消息。可能是std::vector
boost::variant
的{{1}}。
我一直想在过去类似性质的代码中使用alloca
。然而,当人们研究系统编程文献时,总是会对这个函数调用提出大量警告。就个人而言,我不能想到过去15年中没有虚拟内存的服务器类机器,而且我知道Windows堆栈一次只能增长一个虚拟内存,所以我假设Unices也是如此。这里没有砖墙(再也没有),堆栈就像堆一样可能耗尽空间,那么是什么给出了?为什么人们不会超过阿洛卡呢?我可以想到许多负责任地使用alloca的用例(字符串处理任何人?)。
无论如何,我决定测试性能差异(见下文),并且alloca和malloc之间存在5倍的速度差异(测试捕获了我将如何使用alloca)。那么,有变化吗?我们是否应该谨慎对待风并使用alloca
(包裹在std::allocator
),只要我们能够完全确定对象的生命周期?
我厌倦了生活在恐惧中!
修改
好的,所以有限制,对于Windows来说这是一个链接时间限制。对于Unix来说,它似乎是可调的。似乎页面对齐的内存分配器是有序的:D任何人都知道通用的便携式实现:D?
代码:
#include <stdlib.h>
#include <time.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
using namespace boost::posix_time;
int random_string_size()
{
return ( (rand() % 1023) +1 );
}
int random_vector_size()
{
return ( (rand() % 31) +1);
}
void alloca_test()
{
int vec_sz = random_vector_size();
void ** vec = (void **) alloca(vec_sz * sizeof(void *));
for(int i = 0 ; i < vec_sz ; i++)
{
vec[i] = alloca(random_string_size());
}
}
void malloc_test()
{
int vec_sz = random_vector_size();
void ** vec = (void **) malloc(vec_sz * sizeof(void *));
for(int i = 0 ; i < vec_sz ; i++)
{
vec[i] = malloc(random_string_size());
}
for(int i = 0 ; i < vec_sz ; i++)
{
free(vec[i]);
}
free(vec);
}
int main()
{
srand( time(NULL) );
ptime now;
ptime after;
int test_repeat = 100;
int times = 100000;
time_duration alloc_total;
for(int ii=0; ii < test_repeat; ++ii)
{
now = microsec_clock::local_time();
for(int i =0 ; i < times ; ++i)
{
alloca_test();
}
after = microsec_clock::local_time();
alloc_total += after -now;
}
std::cout << "alloca_time: " << alloc_total/test_repeat << std::endl;
time_duration malloc_total;
for(int ii=0; ii < test_repeat; ++ii)
{
now = microsec_clock::local_time();
for(int i =0 ; i < times ; ++i)
{
malloc_test();
}
after = microsec_clock::local_time();
malloc_total += after-now;
}
std::cout << "malloc_time: " << malloc_total/test_repeat << std::endl;
}
输出:
hassan@hassan-desktop:~/test$ ./a.out
alloca_time: 00:00:00.056302
malloc_time: 00:00:00.260059
hassan@hassan-desktop:~/test$ ./a.out
alloca_time: 00:00:00.056229
malloc_time: 00:00:00.256374
hassan@hassan-desktop:~/test$ ./a.out
alloca_time: 00:00:00.056119
malloc_time: 00:00:00.265731
- 编辑:家庭计算机,clang和google perftools上的结果 -
G++ without any optimization flags
alloca_time: 00:00:00.025785
malloc_time: 00:00:00.106345
G++ -O3
alloca_time: 00:00:00.021838
cmalloc_time: 00:00:00.111039
Clang no flags
alloca_time: 00:00:00.025503
malloc_time: 00:00:00.104551
Clang -O3 (alloca become magically faster)
alloca_time: 00:00:00.013028
malloc_time: 00:00:00.101729
g++ -O3 perftools
alloca_time: 00:00:00.021137
malloc_time: 00:00:00.043913
clang++ -O3 perftools (The sweet spot)
alloca_time: 00:00:00.013969
malloc_time: 00:00:00.044468
答案 0 :(得分:15)
首先,即使存在大量虚拟内存并不意味着您的进程将被允许填充它。在* nix上有堆栈大小限制,而堆更宽容。
如果您只需要分配几百/千字节,请务必继续。除此之外的任何事情都将取决于任何给定系统上的限制(ulimit),这只是一个灾难的处方。
Why is the use of alloca() not considered good practice?
在我的工作开发盒(Gentoo)上,我的默认堆栈大小限制为8192 kb。这不是很大,如果alloca溢出堆栈,那么行为是未定义的。
答案 1 :(得分:6)
我认为你需要在理解什么是alloca时要小心一点。与进入堆的malloc,搜索各种缓冲区的存储桶和链接列表不同,alloca只需要使用堆栈寄存器(x86上的ESP)并将其移动到线程堆栈上创建一个“漏洞”,您可以在其中存储您想要的任何内容。这就是为什么它是超快速的,只有一个(或几个)汇编指令。
正如其他人指出的那样,你需要担心的不是“虚拟内存”,而是为堆栈预留的大小。虽然其他人将自己限制在“几百字节”,只要你知道你的应用程序并小心它,我们已经分配了高达256kb没有任何问题(默认堆栈大小,至少对于visual studio,是1mb,你可以随时如果你需要增加它。)
另外你真的不能将alloca用作通用分配器(即将它包装在另一个函数中),因为无论内存分配给你什么,当弹出当前函数的堆栈帧时(即函数时),该内存将会消失退出)。
我也看到有人说alloca不是完全跨平台兼容的,但是如果你正在为特定平台编写特定的应用程序并且你可以选择使用alloca,有时它是你拥有的最佳选择,只要你了解增加堆栈使用的含义。
答案 2 :(得分:4)
首先,这是因为alloca
内存很难控制。它是无类型的,尽早死亡,这使它不是很有帮助。另外,alloca
有一些不幸的副作用,这些副作用是常规堆栈变量现在必须动态索引而不是常量,这甚至会影响你在访问它们的基本操作中的性能并消耗寄存器/堆栈空间存储动态偏移量。这意味着使用alloca
的实际成本不会记录在函数返回所需的时间内。另外,与堆内存相比,堆栈内存非常有限 - 在Windows上,我认为堆栈限制默认为8MB,而堆可以几乎占用整个用户地址空间。更重要的是,最终,无论您要返回的数据都必须在堆上,因此您也可以将其用作工作空间。
答案 3 :(得分:3)
未提出的一点是stack is often contiguous,而堆不是。{3}}。一般来说,堆栈与堆一样可能耗尽内存并不是真的。
在C ++中,很常见的是将对象实例声明为locals,它有点像alloca
但是结构化内存而不是N个字节的块 - 也许您可以将此视为对你的主要观点是,更多地使用基于堆栈的内存是一个好主意。我很快就会这样做(将对象实例声明为RAII本地),而不是在C ++程序中使用malloc
(或alloca
)。所有这些free
调用都会使异常安全......
这通常假定对象的范围仅限于此函数及其被调用的函数。如果情况并非如此,那么使用基于堆栈的内存通常不是一个好主意。
答案 4 :(得分:2)
Windows堆栈没有增长 - 它的保留大小是在链接时设置的,但是这个大小的页面只会在需要时提交。见http://msdn.microsoft.com/en-us/library/ms686774%28v=vs.85%29.asp。由于默认保留大小为1Mb,因此在使用alloca()
时可能会轻易超过此值。