24

我正在开发一个软实时事件处理系统。我想尽量减少我的代码中具有不确定时间的调用。我需要构建一个由字符串、数字、时间戳和 GUID 组成的消息。大概是std::vectorboost::variant

我一直想alloca在过去的代码中使用类似性质的代码。然而,当人们查看系统编程文献时,总是对这个函数调用非常谨慎。就我个人而言,在过去的 15 年中,我无法想到没有虚拟内存的服务器类机器,而且我知道 Windows 堆栈一次增长一个虚拟内存页面,所以我假设Unices也一样。这里(不再)没有砖墙,堆栈和堆一样可能会用完空间,所以什么给了?为什么人们不喜欢 aloca 呢?我可以想到许多负责任地使用 alloca 的用例(字符串处理任何人?)。

无论如何,我决定测试性能差异(见下文),alloca 和 malloc 之间存在 5 倍的速度差异(测试捕获了我将如何使用 alloca)。那么,事情发生了变化吗?每当我们可以绝对确定对象的生命周期时,我们是否应该将谨慎抛诸脑后并使用alloca(包裹在 a 中)?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
4

5 回答 5

18

首先,即使有很多虚拟内存并不意味着您的进程将被允许填充它。在 *nix 上有堆栈大小限制,而堆则更宽容。

如果您只打算分配几百 / 千字节,请确保继续。除此之外的任何事情都将取决于任何给定系统上的限制(ulimit),而这只是灾难的根源。

为什么使用 alloca() 不被认为是好的做法?

在我工作的开发箱(Gentoo)上,我的默认堆栈大小限制为 8192 kb。这不是很大,如果 alloca 溢出堆栈,则行为未定义。

于 2011-04-27T16:53:41.450 回答
6

我认为您在理解 alloca 实际上是什么时需要小心一点。与进入堆的 malloc 不同,它通过存储桶和各种缓冲区的链表进行搜索,alloca 只需获取您的堆栈寄存器(x86 上的 ESP)并移动它以在线程堆栈上创建一个“洞”,您可以在其中存储任何您想要的东西。这就是为什么它超级快,只有一个(或几个)汇编指令。

因此,正如其他人指出的那样,您需要担心的不是“虚拟内存”,而是为堆栈保留的大小。尽管其他人将自己限制为“几百字节”,但只要您了解您的应用程序并注意它,我们已经分配了 256kb 没有任何问题(默认堆栈大小,至少对于 Visual Studio,是 1mb,您总是可以如果需要,请增加它)。

此外,您真的不能将 alloca 用作通用分配器(即将其包装在另一个函数中),因为无论分配给您的内存是什么,当当前函数的堆栈帧被弹出时(即当函数退出时),该内存都会消失。

我也看到有人说alloca不是完全跨平台兼容的,但是如果你正在为特定平台编写特定的应用程序并且你可以选择使用alloca,有时它是你最好的选择,只要您了解增加堆栈使用量的含义。

于 2011-04-27T17:22:12.550 回答
4

首先,这是因为alloca内存很难控制。它是无类型的,尽早死亡,这使得它不是很有帮助。此外,alloca还有一些不幸的副作用,这些副作用是现在必须动态索引常规堆栈变量而不是常量,这会影响您在访问它们的基本操作时的性能,并消耗寄存器/堆栈空间来存储动态偏移量. 这意味着实际使用成本alloca没有记录在函数返回所需的时间。此外,与堆内存相比,堆栈内存非常有限——我相信在 Windows 上,堆栈限制默认为 8MB,而堆几乎可以是整个用户地址空间。不仅如此,最终,您想要返回的任何数据都必须在堆上,因此您不妨将其用作工作空间。

于 2011-04-28T15:49:20.480 回答
3

未做的一点 afai 可以看到,堆栈通常是连续的,而堆则不是。说堆栈和堆一样有可能耗尽内存通常是不正确的。

在 C++ 中,将对象实例声明为局部变量是很常见的,这有点像alloca结构化内存,而不是 N 字节块 - 也许您可以将其视为对您的主要观点的致敬,这是更大的使用基于堆栈的内存是个好主意。我宁愿这样做(将对象实例声明为 RAII 本地),而不是 在 C++ 程序中使用malloc(或)。alloca所有那些free使异常安全的调用......

这通常假设对象的范围仅限于此函数及其调用函数。如果不是这种情况,那么使用基于堆栈的内存通常不是一个好主意。

于 2011-04-27T18:46:52.373 回答
2

Windows 堆栈不会增长 - 它的保留大小是在链接时设置的,但只有在需要时才会提交此大小内的页面。请参阅http://msdn.microsoft.com/en-us/library/ms686774%28v=vs.85%29.asp。由于默认保留大小为 1Mb,因此在使用alloca().

于 2011-04-27T16:57:14.613 回答