关于alloca的使用和滥用

| 我正在开发一个软实时事件处理系统。我想尽量减少代码中具有不确定时间的调用。我需要构造一条由字符串,数字,时间戳和GUID组成的消息。可能是
boost::variant
std::vector
。 我一直想在过去类似性质的代码中使用
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
    
已邀请:
首先,即使有很多虚拟内存并不意味着您的进程将被允许填充它。在* nix上有堆栈大小限制,而堆则宽容得多。 如果您只打算分配几百/千个字节,请确定继续。超出此限制的内容将取决于给定系统上的限制(ulimit),这仅仅是灾难的根源。 为什么不将alloca()用作良好实践? 在工作中的开发箱(Gentoo)上,默认堆栈大小限制为8192 kb。那不是很大,并且如果alloca溢出堆栈,则行为是不确定的。     
我认为您需要稍微了解一下alloca的实际含义。与malloc进入堆,在存储桶和各种缓冲区的链接列表中进行搜索不同,alloca只需获取您的堆栈寄存器(x86上的ESP),然后将其移动以在线程的堆栈上创建一个“ hole”即可存储您想要的任何东西。这就是为什么它只有一个(或几个)汇编指令,速度超快的原因。 因此,正如其他人指出的那样,您不必担心“虚拟内存”,而是为堆栈保留的大小。尽管其他人将自己限制为“几百个字节”,但只要您了解您的应用程序并认真对待它,我们就会分配256kb的内存而不会出现任何问题(默认堆栈大小,至少对于Visual Studio而言为1mb,您可以随时将其增加)。 另外,您真的不能将alloca用作通用分配器(即,将其包装在另一个函数中),因为无论alloca为您分配的内存是什么,当弹出当前函数的堆栈框架时(即,当函数退出时),该内存都将消失。 我也看到有人说alloca并非完全跨平台兼容,但是如果您正在为特定平台编写特定的应用程序,并且可以选择使用alloca,那么有时这是您拥有的最佳选择,只要您了解增加堆栈使用量的含义即可。     
首先,是因为很难控制“ 2”内存。它是无类型的,最早会死掉,这使其不是很有帮助。此外,ѭ2还有一些不幸的副作用,这些副作用是现在必须动态索引常规堆栈变量而不是常量,这甚至会影响基本操作访问它们的性能,并占用寄存器/堆栈空间来存储动态变量。抵消。这意味着使用
alloca
的实际成本不会仅仅在函数返回所花费的时间内记录下来。此外,与堆内存相比,栈内存非常有限-我相信在Windows上默认情况下,栈限制为8MB,而堆几乎可以是整个用户地址空间。不仅如此,最终,无论您要返回什么数据,都必须放在堆中,因此您最好将其用作工作空间。     
afai尚未说明的一点是,堆栈通常是连续的,而堆不是。通常说堆栈不如堆快用完内存是不正确的。 在C ++中,通常将对象实例声明为locals,这有点像
alloca
,但具有结构化内存,而不是N字节的块-也许您可以认为这是对您的主要观点的敬意,这就是更多地使用基于堆栈的内存是一个好主意。我会比在C ++程序中使用
malloc
(或
alloca
)更早地做到这一点(将对象实例声明为RAII本地)。所有那些
free
调用都使异常安全... 这通常假定对象的范围仅限于此功能及其调用的功能。如果不是这种情况,那么使用基于堆栈的内存通常不是一个好主意。     
Windows堆栈不会增长-它的保留大小是在链接时设置的,但是该大小内的页面只会根据需要提交。请参阅http://msdn.microsoft.com/zh-cn/library/ms686774%28v=vs.85%29.asp。由于默认保留大小为1Mb,因此使用
alloca()
可以轻松超过该大小。     

要回复问题请先登录注册