即使使用new和delete,零Functor构造和开销?

时间:2009-05-11 01:58:08

标签: c++ optimization compiler-construction functor

如果我有一个没有状态的仿函数类,但是我是用new创建它的,那么典型的编译器是否足够智能以完全优化创建开销?

制作一堆无状态仿函数时出现了这个问题。如果它们被分配在堆栈上,那么它们的0状态类体是否意味着堆栈确实根本没有改变?看来你必须以后再获取仿函数实例的地址。 堆分配也是一样。

在这种情况下,仿函数总是在创建中添加(微不足道但非零)的开销。但也许编译器可以看到是否使用了地址,如果没有,它可以消除堆栈分配。 (或者,它甚至可以消除分配?)

但是作为临时创建的仿函数呢?

#include <iostream>

struct GTfunctor 
{
  inline bool operator()(int a, int b) {return a>b; }
};

int main()
{
  GTfunctor* f= new GTfunctor;
  GTfunctor g;

  std::cout<< (*f)(2,1) << std::endl;
  std::cout<< g(2,1) << std::endl;
  std::cout<< GTfunctor()(2,1) << std::endl;
  delete f;
}

因此,在上面的具体示例中,三行各自以三种不同的方式调用相同的仿函数。在这个例子中,这些方式之间是否存在效率差异?或者编译器是否能够将每一行优化为无计算的打印语句?

编辑:   大多数答案都说编译器永远不会内联/消除堆分配的仿函数。但这也是真的吗?大多数编译器(GCC,MS,Intel)也具有链接时间优化,这确实可以进行优化。 (但是吗?)

9 个答案:

答案 0 :(得分:3)

  

是否足够智能,能够完全优化创建开销?

当你在堆上创建它们时,我怀疑编译器是否被允许。 IMO:

  • 调用new意味着调用operator new。
  • operator new是运行时库中定义的非平凡函数。
  • 不允许编译器决定你真的不想调用这样的函数,并决定作为优化它将默默地不调用它。

当你在堆栈上创建它们而不是它们的地址时,可能......或者可能不是:我的猜测是每个对象都有一个非零大小,以便占用一些内存,按顺序即使对象没有与其身份相同的状态,也要有一个身份。

答案 1 :(得分:3)

显然,这取决于你的编译器。

我会说

  • 编译器将优化堆上的对象。 (这是因为,正如ChrisW所说,编译器永远不会优化对new的调用,这几乎肯定是在另一个翻译单元中定义的。)

  • 一些编译器将优化堆栈上的命名对象。我已经知道gcc经常进行这种优化。

  • 大多数编译器将优化堆栈中未命名的对象。这是“标准”C ++优化之一,尤其是当更高级的C ++用户倾向于创建大量未命名的临时变量时。

不幸的是,这些只是经验法则。众所周知,优化器是不可预测的;真正了解编译器正在做什么的唯一方法是读取汇编输出。

答案 2 :(得分:1)

我非常怀疑这种类型的优化是否允许,但如果您的仿函数没有状态,为什么要在堆上初始化它?它应该像临时一样容易使用。

答案 3 :(得分:1)

C ++对象的大小始终不为零。 “空基类优化”允许空基类的大小为零,但这里不适用。

我没有参与任何C ++优化器,所以我说的只是推测。我认为第2和第3将很容易内联扩展,没有开销,也没有创建GTFunctor。然而,仿函数指针是一个不同的故事。在您的示例中,它可能看起来很简单,任何优化器都应该能够消除堆分配,但是在一个非平凡的程序中,您可以在一个转换单元中创建仿函数并在另一个转换单元中使用它。或者甚至在编译器/链接器/加载器/运行时系统没有源代码的不同库中,并且几乎不可能进行优化。鉴于优化它并不容易,性能的潜在增益并不大,并且在堆中分配空仿函数的情况可能很少,我认为大多数优化器程序员可能不会将此优化放在他们的待办事项列表。

答案 4 :(得分:1)

编译器无法优化对new或delete的调用。但是它可以优化堆栈上创建的变量,因为它没有状态。

答案 5 :(得分:1)

回答堆问题的简单方法:

GTfunctor *f = new GTfunctor;

f的值不能为空,那应该是什么?你也有:

GTfunctor *g = new GTfunctor;

现在g的值不能等于f的值,那么每个值应该是多少?

此外,fg都不等于从new获得的任何其他指针,除非其他地方的某个指针以某种方式初始化为等于f或{ {1}},(取决于之后的代码)可能涉及检查程序的其余部分的作用。

是的,如果通过本地检查代码,编译器可以看到您从不依赖任何这些要求,那么它可以执行重写,以便不会发生堆分配。问题是,如果你的代码很简单,你可能会自己重写并最终得到一个更易读的程序,例如您的测试程序看起来像基于堆栈的g示例。因此,真正的程序不会受益于编译器中的这种优化。

大概是你这样做的原因是因为有时候算人 会有数据,这取决于在运行时选择的类型。因此,编译时分析在这里无法发挥作用。

答案 6 :(得分:1)

C ++标准规定每个对象(堆上的imho)必须至少具有一个字节的大小,因此它可以被唯一地寻址。

使用new生成仿函数会导致两个问题:

  1. 通常不能优化构造。 New是具有复杂副作用的函数(bad_alloc)。
  2. 因为您间接地对仿函数求助,所以编译器可能无法内联函数。
  3. 如果你在堆栈中生成仿函数,你很可能看不到仿函数的迹象。

    附注:内联声明不是必需的。在类定义中定义的每个函数都被视为可内联。

答案 7 :(得分:0)

编译器可能会发现operator()不使用任何成员变量,并将其优化到最大值。但是,我不会对本地或堆分配的变量做任何假设。

编辑:如有疑问,请打开编译器上的汇编输出选项,看看它实际上在做什么。当你能够自己看到真正的答案时,在网上听一群白痴是没有意义的。

答案 8 :(得分:0)

你的问题的答案有两个方面。

  1. 编译器是否优化了堆分配:我强烈怀疑它,但我不是一个标准的人,所以我必须查找它。

  2. 编译器可以通过内联对象的operator()进行优化吗?是。只要您不将调用指定为虚拟,即使实际上没有执行指针解除引用。