这是使用alloca的一个很好的理由吗?

时间:2013-04-30 18:43:27

标签: c++ alloca

我有以下功能:

double 
neville (double xx, size_t n, const double *x, const double *y, double *work);

使用xxn中存储的x点在y执行拉格朗日插值。 work数组的大小为2 * n。由于这是多项式插值,n在大约5的范围内,很少超过10。

此功能经过积极优化,应该在紧密循环中调用。分析表明在循环中分配工作数组的堆很糟糕。不幸的是,我应该把它打包成一个类似函数的类,客户端必须不知道工作数组。

目前,我使用模板整数参数作为度和std::array以避免动态分配work数组:

template <size_t n>
struct interpolator
{
    double operator() (double xx) const
    {
        std::array<double, 2 * n> work;
        size_t i = locate (xx); // not shown here, no performance impact
                                // due to clever tricks + nice calling patterns

        return neville (xx, n, x + i, y + i, work.data ());
    }        

    const double *x, *y;
};

将工作数组存储为类的可变成员是可能的,但operator()应该由多个线程同时使用。如果您在编译时知道n,则此版本可以。

现在,我需要在运行时指定n参数。我想知道这样的事情:

double operator() (double xx) const
{
    auto work = static_cast<double*> (alloca (n * sizeof (double)));
    ...

使用alloca时有些铃声响起:我当然会在n上设置上限以避免alloca调用溢出(无论如何,使用100度是非常愚蠢的多项式插值)。

然而,我对这种方法很不满意:

  • 我是否遗漏了alloca的明显危险?
  • 这里有更好的方法来避免堆分配吗?

3 个答案:

答案 0 :(得分:5)

  然而,我对这种方法很不满意:

     
      
  • 我错过了一些明显的alloca危险吗?
  •   

你指出了一个真正的危险:alloca未定义堆栈溢出行为。此外,alloca实际上并未标准化。例如,Visual C ++代替_allocaGCC by default defines it as a macro。但是,通过在现有的几个实现中提供一个薄的包装器,可以相当容易地规避这个问题。

  
      
  • 这里有更好的方法来避免堆分配吗?
  •   

不是真的。 C ++ 14将有一个(可能!)堆栈分配的可变长度数组类型。但是在那之前,当你认为std::array不适合时,请在你的情况下使用alloca

虽然有点挑剔:您的代码缺少返回值alloca的强制转换。它甚至不应该编译。

答案 1 :(得分:2)

总有很多笔记要添加到堆栈内存的任何使用中。正如您所指出的,当空间耗尽时,堆栈具有有限的大小和相当严重的错误行为。如果有防护页面,堆栈溢出肯定会崩溃,但在某些平台和线程环境有时可能是静默损坏(坏)或安全问题(更糟)。

还要记住,与malloc相比,堆栈分配非常快,(它只是从堆栈指针寄存器中减去)。但该内存的使用可能不是。将堆栈帧大幅下降的副作用是您要调用的叶函数的缓存行不再驻留。因此,任何对该内存的使用都需要转到SMP环境,以使高速缓存行恢复到独占(在MESI意义上)状态。 SMP总线比L1缓存更受约束(!)环境,如果你在垃圾邮件堆栈框架周围这可能是一个真正的可扩展性问题。

另外,就语法而言,请注意gcc和clang(以及我认为的Intel编译器)都支持C99可变长度数组语法作为C ++扩展。您可能根本不需要实际调用libc alloca()例程。

最后,请注意malloc真的不那么慢。如果你正在处理数十千字节或更大领域的单个缓冲区,那么为你将要做的任何工作所需的内存带宽将会淹没malloc的任何开销。

基本上:alloca()很可爱,并且有其用途,但除非您准备好基准测试证明您需要它,否则您可能不会并且应该坚持使用传统分配。

答案 2 :(得分:1)

这个怎么样:

double operator() (double xx) const
{
    double work_static[STATIC_N_MAX];
    double* work = work_static;
    std::vector<double> work_dynamic;

    if ( n > STATIC_N_MAX ) {
        work_dynamic.resize(n);
        work = &work_dynamic[0];
    }

    ///...

n太大时,没有非便携式功能,异常安全,并且会优雅地降级。当然,您可以将work_static设为std::array,但我不确定您在此中看到了什么好处。