【问题标题】:zero Functor construction and overhead even with new and delete?即使使用 new 和 delete,Functor 构造和开销也为零?
【发布时间】:2009-05-11 01:58:07
【问题描述】:

如果我有一个没有状态的仿函数类,但我使用 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)也有链接时间优化,它们确实可以进行这种优化。 (但有吗?)

【问题讨论】:

  • 或许将其声明为静态函数会更好。
  • operator() 不能成为静态函数。
  • 正确答案是#2 或#3。 (#3可能会更快,具体取决于优化器,但#2看起来更好/更干净)。 可能仍然在堆栈上分配空间,但与堆分配相比,4 个字节不算什么...

标签: c++ optimization compiler-construction functor


【解决方案1】:

典型的编译器是否足够聪明,可以完全优化创建开销?

当您在堆上创建它们时,我怀疑是否允许编译器这样做。国际海事组织:

  • 调用 new 意味着调用 operator new。
  • operator new 是运行时库中定义的重要函数。
  • 不允许编译器判断您并不是真的要调用这样的函数,并决定作为优化它不会默默地不调用它。

当您在堆栈上创建它们并且不获取它们的地址时,那么可能......或者可能不是:我的猜测是每个对象都有一个非零大小,以便占用一些内存,以便拥有一个身份,即使对象除了身份之外没有任何状态。

【讨论】:

    【解决方案2】:

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

    我会说

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

    • 一些编译器会优化掉堆栈上的命名对象。我知道 gcc 经常做这种优化。

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

    很遗憾,这些只是经验法则。优化器是出了名的不可预测。真正了解编译器在做什么的唯一方法是读取汇编输出。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 如果您正在处理许多仿函数并希望以相同的方式处理它们,那么在堆上分配可能很有用。一个有趣的问题是,冗余堆分配的成本有多大,编译器是否认识到它的无用性质并允许消除它的开销。
      • "如果您正在处理许多函子并希望以同样的方式对待它们" 您的意思是您正在多态地使用函子吗?带虚函数?如果是这样,也许优化是做静态多态。使用静态多态性,您通常可以更快地交换可执行映像大小。
      【解决方案4】:

      C++ 对象的大小总是非零。 “空基类优化”允许空基类的大小为零,但这不适用于此处。

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

      【讨论】:

      • 确实,所有对象的大小都是非零的,但前提是编译器实际构造了它们。在代码示例中,第三次调用创建了一个临时对象,优化器几乎肯定会内联其函数调用,并且从不实际创建对象。它可以对堆栈和堆示例执行相同的操作,但是会这样做,如果不是,是因为它是非法的还是因为即使在这个简单的示例中也很难确定?
      • 我认为堆栈对象可以很容易地优化掉,因为编译器会生成代码来分配和释放自动对象,因此它可以完全控制。堆对象不同,因为它是执行新/删除的运行时库。编译器很难知道 new/delete 是否有除了构造函数/分配和调用析构函数/释放之外的副作用。更糟糕的是 operator new 和 operator delete 可以被覆盖。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        回答堆问题的简单方法:

        GTfunctor *f = new GTfunctor;
        

        f的值不能为null,那应该是什么呢?你还有:

        GTfunctor *g = new GTfunctor;
        

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

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

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

        您这样做的原因可能是因为有时仿函数 确实 有数据,具体取决于在运行时选择的类型。所以编译时分析在这里不能有效地发挥它的魔力。

        【讨论】:

          【解决方案7】:

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

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

          1. 构造通常无法优化掉。 New 是一个具有复杂副作用的函数 (bad_alloc)。
          2. 由于您间接寻址仿函数,编译器可能无法内联该函数。

          如果您在堆栈上生成函子,您很可能看不到函子的迹象。

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

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

              【解决方案9】:

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

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

              2. 编译器能否通过内联对象的 operator() 进行优化?是的。只要您不将调用指定为虚拟调用,就不会实际执行指针取消引用。

              【讨论】:

                猜你喜欢
                • 2014-12-28
                • 1970-01-01
                • 2014-12-12
                • 2023-03-22
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多