【问题标题】:Increase stack size to use alloca()?增加堆栈大小以使用 alloca()?
【发布时间】:2013-03-15 15:19:27
【问题描述】:

这是两个重叠的问题——我希望尝试将 alloca() 用于大型数组,而不是在堆上分配动态大小的数组。这样我就可以提高性能而不必进行堆分配。但是,我得到的印象堆栈大小通常很小?增加堆栈大小以便我可以充分利用 alloca() 是否有任何缺点?是不是我拥有的 RAM 越多,我的堆栈大小就可以按比例增加越大?

EDIT1:最好是 Linux

EDIT2:我没有指定尺寸 - 我更想知道如何判断是什么决定了限制/边界。

【问题讨论】:

  • alloca 的一个缺点是它不能检测分配失败。如果您尝试分配过多的堆栈空间,则行为未定义,您的程序可能会崩溃。
  • @KeithThompson - 使用 Linux malloc 并没有更好,只要您在完整地址空间内请求一个数量,它几乎总是返回一个有效指针。在最近的一次测试中,我能够成功请求 137435723384 kB,然后我得到一个 NULL 回复(!)。
  • @teppic:这是 Linux 上的默认行为。但是,如果您愿意,您可以打开一致的(即“健全的”)行为。

标签: c++ c performance memory memory-management


【解决方案1】:

在大多数 unix-y 平台上,堆栈大小(默认)为 8MB,在 Windows 上为 1MB(即,因为 Windows 有 a deterministic way for recovering from out of stack problems,而 unix-y 平台通常会抛出一个通用的 SIGSEGV 信号)。

如果您的分配量很大,那么在堆上分配与在堆栈上分配之间不会有太大的性能差异。当然,堆栈每次分配的效率稍高一些,但如果您的分配很大,则分配的数量可能会很少。

如果您想要一个更大的类似堆栈的结构,您始终可以编写自己的分配器,它从 malloc 获取一个大块,然后以类似堆栈的方式处理分配/解除分配。

#include <stdexcept>
#include <cstddef>

class StackLikeAllocator
{
    std::size_t usedSize;
    std::size_t maximumSize;
    void *memory;
public:
    StackLikeAllocator(std::size_t backingSize)
    {
        memory = new char[backingSize];
        usedSize = 0;
        maximumSize = backingSize;
    }
    ~StackLikeAllocator()
    {
        delete[] memory;
    }
    void * Allocate(std::size_t desiredSize)
    {
        // You would have to make sure alignment was correct for your
        // platform (Exercise to the reader)
        std::size_t newUsedSize = usedSize + desiredSize;
        if (newUsedSize > maximumSize)
        {
            throw std::bad_alloc("Exceeded maximum size for this allocator.");
        }

        void* result = static_cast<void*>(static_cast<char*>(memory) + usedSize);
        usedSize = newUsedSize;
        return result;
    }

    // If you need to support deallocation then modifying this shouldn't be
    // too difficult
}

【讨论】:

  • 您能否详细说明最后一段?
  • 如果你在 Windows 上遇到堆栈溢出,观察到异常后你的选择是什么?您不会自动为您扩大堆栈,手动执行此操作会使您获得一个不连续的堆栈,您的程序无论如何都不太可能继续工作。
  • @Alexy:你不能继续导致溢出的代码,不。但是,如果您是库,您可以确定性地防止进程被破坏和/或向客户端报告合理的“内存不足”错误代码。例如,boost::regex 的默认正则表达式执行器是基于堆栈的,而在 Windows 上,它们捕获 EXCEPTION_STACK_OVERFLOW 并使用它来摆脱正则表达式变得病态。
  • 所以,这还不是完全恢复,而是检测。
  • @Alexey:我称之为“进程没有被破坏”恢复。是的,你可以用完堆栈。但对于任何分配策略都是如此。你可以得到std::bad_alloc,你也不能从中“恢复”。它的工作方式相同。
【解决方案2】:

程序的主线程获取的默认堆栈大小是特定于编译器(和/或特定于操作系统)的东西,您应该查看相应的文档以了解如何扩大堆栈。

您可能无法将程序的默认堆栈放大到任意大的尺寸。

但是,正如已经指出的那样,您可以在运行时创建一个线程,并使用所需大小的堆栈。

无论如何,alloca() 与一次分配的大缓冲区相比并没有太大的好处。您无需多次释放和重新分配它。

【讨论】:

  • 堆栈大小不是特定于编译器的。编译器对堆栈大小一无所知。
  • @JohannesOvermann 哦,真的吗? Borland/Turbo C/C++ 有一个名为_stklen 的变量,它使用堆栈大小进行初始化。 PE 图像格式中有两个字段:STACK RESERVE SIZESTACK COMMIT SIZE,这就是 Microsoft 的 C/C++ 编译器生成的。
  • @Alexey:它可能只是给出了默认的堆栈大小。用户可以使用他们想要的任何堆栈大小调用CreateThread,这就是您将得到的,而不管编译器怎么想。
  • @BillyONeal 这是一个不同的堆栈,在运行时创建。而且我们不知道是否可以在运行时创建线程来获得足够大的堆栈。
  • @Alexey:这并没有改变编译器最终不会选择堆栈大小。该平台会这样做。
【解决方案3】:

alloca()new / malloc() 之间最重要的区别在于,当您从当前函数返回时,使用alloca() 分配的所有内存都将消失。

alloca() 仅对小型临时数据结构有用。

它只对小数据结构有用,因为大数据结构会破坏堆栈的缓存局部性,这会给您带来相当大的性能损失。数组作为局部变量也是如此。

仅在非常特殊的情况下使用alloca()。如果不确定,请不要使用它。

一般规则是:不要将大数据结构 (>= 1k) 放在堆栈上。堆栈不缩放。这是一种非常有限的资源。

【讨论】:

    【解决方案4】:

    回答第一个问题:堆栈大小通常比堆大小小(这在大多数 Linux 应用程序中都是如此)。

    如果您计划的分配相对于实际默认堆栈大小相对很大,那么我认为使用堆中的动态分配会更好(而不是尝试增加堆栈大小)。使用内存(填充、读取、操作)的成本可能会远远超过分配的成本。在这种情况下,从堆栈分配您不太可能看到可衡量的好处。

    【讨论】:

      猜你喜欢
      • 2015-11-22
      • 2010-11-24
      • 2012-02-10
      • 2017-06-08
      • 1970-01-01
      • 2011-04-03
      • 2013-12-24
      • 2011-09-29
      • 2017-03-02
      相关资源
      最近更新 更多