这是使用alloca的好理由吗?

  • 本文关键字:理由 alloca c++ alloca
  • 更新时间 :
  • 英文 :


我有以下功能:

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

,利用xy中存储的n点对xx进行拉格朗日插值。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的一些明显危险吗?
  • 是否有更好的方法来避免堆分配?

然而,我对这种方法感到很不舒服:

  • 我是否错过了一些明显的分配危险?

你指出了一个真正的危险:alloca没有定义堆栈溢出行为。此外,alloca实际上并没有标准化。例如,Visual c++有_alloca,而GCC在默认情况下将其定义为宏。但是,通过为少数现有实现提供一个薄包装器,可以相当容易地规避这个问题。

  • 是否有更好的方法来避免堆分配?

没有。c++ 14将有一个(潜在的!)堆栈分配的可变长度数组类型。但在那之前,当你认为std::array不太合适时,在你这样的情况下,选择alloca

一个小问题:你的代码缺少对alloca返回值的强制转换。它甚至不能编译

对于堆栈内存的任何使用,总是有一堆注释要添加。正如你所指出的,堆栈的大小是有限的,当空间耗尽时,会出现相当严重的错误行为。如果有保护页,堆栈溢出可能会崩溃,但在某些平台和线程环境中,有时可能是无声的损坏(坏)或安全问题(更糟)。

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

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

最后,请注意malloc真的不是那么慢。如果您正在处理几十kb或更大的单个缓冲区,那么为您要在其上执行的任何工作提供服务所需的内存带宽将淹没malloc的任何开销。

基本上:alloca()是可爱的,有它的用途,但除非你有一个基准准备证明你需要它,你可能不应该,应该坚持传统的分配。

这个怎么样:

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,但我不确定您从中看到了什么好处。

相关内容

  • 没有找到相关文章

最新更新