将 alloca() 用于可变长度数组是不是比在堆上使用向量更好?

Posted

技术标签:

【中文标题】将 alloca() 用于可变长度数组是不是比在堆上使用向量更好?【英文标题】:Is using alloca() for variable length arrays better than using a vector on the heap?将 alloca() 用于可变长度数组是否比在堆上使用向量更好? 【发布时间】:2018-03-09 23:05:34 【问题描述】:

我有一些使用可变长度数组 (VLA) 的代码,可以在 gcc 和 clang 中正常编译,但不适用于 MSVC 2015。

class Test 
public:
    Test() 
        P = 5;
    
    void somemethod() 
        int array[P];
        // do something with the array
    
private:
    int P;

代码中似乎有两种解决方案:

使用alloca(),考虑the risks of alloca,绝对确保不访问数组外的元素。 使用 vector 成员变量(假设向量和 c 数组之间的开销不是限制因素,只要 P 在构造对象后保持不变)

ector 会更便携(更少#ifdef 测试使用哪个编译器),但我怀疑alloca() 更快。

矢量实现如下所示:

class Test 
public:
    Test() 
        P = 5;
        init();
    
    void init() 
        array.resize(P);
    
    void somemethod() 
        // do something with the array
    
private:
    int P;
    vector<int> array;

另一个考虑:当我只在函数之外更改 P 时,堆上的数组是否比堆栈上的 VLA 更快地没有重新分配?

最大 P 约为 400。

【问题讨论】:

C++ 没有可变长度数组。 GCC 和 Clang 仅将其作为扩展提供。所以不要对便携性屏住呼吸。 虽然alloca 是一个事实上的 标准,但它不是可移植的,因为实现在报告失败的方式或是否报告失败方面存在差异。你也不想吃掉机器堆栈。使用std::vector 为什么是static?该数组也不是static “我可能想要尽可能多的性能” - 您是否对这两种解决方案进行了分析?您会惊讶于哪些假设在您的评估中不成立。 @allo - “堆栈与堆”是一个错误的问题。确实如此。有很多快速“堆”分配器设计。 【参考方案1】:

您可以并且可能应该使用一些动态分配的heap 内存,例如由std::vector 管理(如answered by Peter)。您可以使用不应该忘记释放 (delete,free,....) 的智能指针或普通原始指针 (new, malloc,....)。请注意,堆分配可能比您认为的要快(实际上,在当前的笔记本电脑上,大多数情况下,不到一微秒)。

有时您可以将分配移出某个内部循环,或者只是偶尔增加它(所以对于类似realloc 的东西,最好使用unsigned newsize=5*oldsize/4+10; 而不是unsigned newsize=oldsize+1;,即有一些几何增长)。如果您不能使用向量,请确保将分配的大小和使用的长度分开(就像 std::vector 内部所做的那样)。

另一种策略是小尺寸与大尺寸的特殊情况。例如对于少于 30 个元素的数组,使用调用堆栈;对于更大的,使用堆。

如果你坚持在call stack 上分配(使用VLAs - 它们是标准C++11 或alloca 的常用扩展),明智的做法是将调用帧限制在几千字节.总调用堆栈受限(例如,在许多笔记本电脑上通常为大约 1 MB 或其中的几个)到某些特定于实现的限制。在某些操作系统中,您可以提高该限制(另请参阅 Linux 上的 setrlimit(2))

请务必在手动调整代码之前进行基准测试。在进行基准测试之前不要忘记启用compiler optimization(例如g++ -O2 -Wall 和GCC)。请记住,caches misses 通常比堆分配要昂贵得多。不要忘记,开发人员的时间也有一些成本(通常与累积的硬件成本相当)。

请注意,使用static variable 或数据也有问题(它不是reentrant,不是thread 安全的,不是异步信号安全的 - 请参阅signal-safety(7) ....)并且可读性和更少健壮。

【讨论】:

我的第一个修复是在方法中使用 malloc/free,但这当然很慢。所以要么是一个静态变量(然后可能是一个 STL 向量就足够快了)或者像 alloca 之类的东西。第三种选择是使用大小为 MAX_P 的数组。 总调用堆栈是有限的(例如大约 1 MB 或其中几个)。 这高度依赖于实现 - 包括操作系统和应用程序。例如,a 32-bit Linux process 最多有 1 GB 用于堆,但最多可以有 2 GB 用于堆栈。 是的,我知道。 我知道你知道。但大多数读者不会。太多的人可能陷入了“堆栈总是很小,堆总是更大”的谬论。使用巨大的预分配堆栈和 VLA/alloca() 是从内存需求已知的多线程内存密集型应用程序中获得更好性能的一种方法。 @BasileStarynkevitch 大部分时间堆栈都小于堆。您指的是实际的“正常”使用情况还是限制?对于大多数此类情况下的 64 位进程,堆和堆栈的实际 de facto limit 是相同的:进程可用的虚拟内存量。正如我之前提到的,对于 32 位 Linux 进程,堆栈可能是堆的两倍。【参考方案2】:

首先,如果您的代码能够按原样使用任何 C++ 编译器进行编译,那么您会很幸运。 VLA 不是标准的 C++。 Some compilers 支持它们作为扩展。

使用alloca() 也不是标准的,因此不能保证在使用不同的编译器时可靠(甚至根本不)工作。

在许多情况下不建议使用static 向量。在您的情况下,它提供的行为可能不等同于原始代码。

您可能希望考虑的第三个选项是

 // in definition of class Test
void somemethod()

    std::vector<int> array(P);      // assume preceding #include <vector>
    // do something with array

向量本质上是一个动态分配的数组,但在函数返回时会在上面正确清理。

以上是标准C++。除非您执行了提供性能问题证据的严格测试和分析,否则这应该足够了。

【讨论】:

这个解决方案就像malloc/free 在每次通话中都做得太慢了。你能详细说明静态向量不等价吗? @allo static 意味着整个程序只有一个副本,因此如果您的对象同时存在两个实例,它将无法正常运行 使用矢量并不等同于使用malloc()free()。无论如何,您需要避免动态内存分配的假设是有缺陷的。除非您通过测试/分析获得证据,否则您所做的只是过早的优化。而且,根据您的编译器和主机系统,做出这样的假设很可能会降低性能。 @allo 但是如果整个程序只有一个,那么将其设为非静态类成员不会有任何损失 @allo - 也许,也许不是。您似乎试图对什么给出或没有给出最佳性能(静态、类成员、动态内存分配等)做出全面的假设。现代系统不可能有这样的一揽子声明,因此需要测试/配置文件。现代编译器和 CPU 可以并且确实打破了程序员可能做出的大量假设。【参考方案3】:

为什么不将数组设为私有成员?

#include <vector>

class Test

public:
    Test()
    
        data_.resize(5);
    
    void somemethod()
    
        // do something with data_
    
private:
    std::vector<int> data_;

由于您指定了数组的可能最大大小,您还可以查看 boost::small_vector 之类的内容,其用法如下:

#include <boost/container/small_vector.hpp>

class Test

public:
    Test()
    
        data_.resize(5);
    
    void somemethod()
    
        // do something with data_
    
private:
    using boc = boost::container;

    constexpr std::size_t preset_capacity_ = 400;
    boc::small_vector<int, preset_capacity_> data_;

您应该分析一下这是否真的更好,并注意这可能会使用更多内存,如果有很多 Test 实例,这可能是一个问题。

【讨论】:

以上是关于将 alloca() 用于可变长度数组是不是比在堆上使用向量更好?的主要内容,如果未能解决你的问题,请参考以下文章

我们可以在 c++20 协程中使用 alloca() 或可变长度数组扩展吗?

使用可变长度数组是不是安全?

alloca 是完全可以替换的吗?

用于可变长度参数数组的 PHPDoc

通过过度分配内存在结构中内联可变长度数组是不是有效?

C有stackalloc函数吗?