【问题标题】:How much of an increase in size do inline functions cause?内联函数会导致大小增加多少?
【发布时间】:2011-07-07 07:16:27
【问题描述】:

我最近开始制作 GTK+ 的 C++ 包装器(没什么特别的,只是将所有内容都包装到 C++ 类中以便于开发,供内部使用)并尽量减少已经缓慢的性能膨胀> Gtk+ 我几乎在所有地方都使用了内联函数。看看几个类函数...

class Widget : public Object
{
public: // A few sample functions. gwidget is the internal GTK+ widget.
    void Show(void) { gtk_widget_show(GTK_WIDGET(gwidget)); }
    void ShowNow(void) { gtk_widget_show_now(GTK_WIDGET(gwidget)); }
    void Hide(void) { gtk_widget_hide(GTK_WIDGET(gwidget)); }
    void ShowAll(void) { gtk_widget_show_all(GTK_WIDGET(gwidget)); }
public: // the internal Gtk+ widget.
    GtkWidget* gwidget;
};

虽然几乎不存在性能膨胀并且启动时间和内存使用量完全相同,但文件大小却急剧增加。 C Gtk+ 示例窗口生成 6.5 kb 而使用我的包装器的示例窗口生成 22.5 kb. ,所以我需要一些建议。我应该继续使用内联函数吗?我希望我的应用程序高效,并且我可以在文件大小上做出一些妥协,即使使用我的包装器生成的 6.5 kb C GTK+ 程序400-500 kb强>但不是更多。我不希望我的包装器生成像 wxWidgets 或 MFC 这样的 HUGE EXES。那么使用内联函数值得还是我应该使用普通函数?

注意:我的所有函数只占用一行或有时两行,并且并不像您在示例中看到的那样大。

【问题讨论】:

  • 你在这里比较什么......你说“一个 C Gtk+ 示例窗口生成 6.5 kb,而使用我的包装器的示例窗口生成 22.5 kb”......你不是在比较用编译的程序你是针对你的 C++ 版本的 C 编译器吗?因为更丰富的运行时环境、它的初始化、异常支持代码、标准库函数等将是一次性成本。您只能使用相同的基本程序工具/编译器/编译器标志进行有意义的比较,然后与/没有你的包装......
  • 我正在使用 gnu g++(使用 MinGW)进行编译。所以两者都被视为 c++ 程序。 C GTK+ 示例窗口是指直接使用 c gtk+ 函数和宏制作的示例窗口。
  • 如果内联函数只包含另一个函数调用(对gtk_widget_show 等),那么额外的大小可能是由其他原因引起的。但他们没有,他们也做GTK_WIDGET,这应该是微不足道的,但可能不是。检查发出的代码或预处理后但编译前的代码(gcc -E),查看对这些成员函数的内联调用是否实际上包含大量代码。
  • @Steve 你的意思是函数宏 GTK_WIDGET 也添加到我的内联函数中?我知道了。我会检查的
  • 当 1) 你想要的东西已经存在并且正在积极开发中时,你为什么要这样做:gtkmm.org/en 和 2) GTK 被设计为不必手动包装,但能够生成绑定自动转换成其他语言!

标签: c++ gtk


【解决方案1】:

我强烈怀疑您是在将苹果与橙子进行比较。

您是否使用完全相同的编译器、相同的编译标志以及制作具有完全相同相同功能的应用程序进行编译?

如果是这样,反汇编可执行文件,看看额外的代码是什么

我的猜测是,它是一些一次性的库代码,用于支持以前未使用的 C++ 功能。

但一如既往,不要猜测,要衡量。

您只有一个数据点。这并不能告诉你太多。 在所有情况下,我们可能会看到文件大小增加了 350%,或者我们可能会看到固定的 16kb 开销。你需要找出它是什么。 所以得到更多的数据点。扩展您的应用程序。让它打开十个窗口而不是一个,或者添加额外的功能。在这种情况下,“你的”版本是否也有三倍大?还是大 16kb?或者介于两者之间?获取更多数据点,您将能够看到文件大小如何缩放

但最有可能的是,您什么都不担心,原因如下:

  • C++ 编译器将内联视为提示。您使编译器可以轻松地内联函数,但决定权在于编译器本身,它会尝试使应用程序快速运行。如果文件大小开始失控,那将会减慢您的代码速度,因此您的编译器将尝试针对更小的文件大小进行更多优化。
  • 您正在查看几个 千字节。在 TB 硬盘的时代。如果这有可能成为问题,那么您应该能够在测试用例中引发该问题。如果您无法编写导致文件大小增长超过 16kb 的测试,则无需担心。
  • 如果文件大小确实成为问题,编译器通常有一个“优化大小”标志。
  • 大型可执行文件通常会变大,因为它们包含大量数据和资源。代码本身很少会成为问题(除非您完全不喜欢模板元编程)

【讨论】:

  • 第 2 点:我知道,我一直对尺寸和性能有点过于痴迷,这可能是因为我尝试学习的第一门语言是 ASM。更不用说我故意使用我的旧 Pentium 4 来编程并尽可能多地使用 C/C++,即使我知道 C#/Java 和 Python。
  • @burningprodigy:这些都没有理由过于痴迷于尺寸和性能。两者都很重要,但仅限于重要的地方。在没有任何区别的情况下沉迷于它们被简单地称为“浪费你的时间”。 特别是,如果您询问经验丰富的性能专家。作为开发人员,您永远没有时间完全优化所有内容。因此,每花一分钟优化代码中的一个地方,就花在优化代码的其他部分上。因此,如果您真正关心大小和性能,则需要选择您的战斗,并在重要的地方进行优化
  • 试图优化代码的每一个部分只会让你得到具有讽刺意味的缓慢而臃肿的代码。因为这意味着您只是没有时间优化代码的重要部分。
  • 好的。所以也许我应该完成我的程序然后进行优化。
【解决方案2】:

大小差异更可能是由于为 C++ 引入的库,而不是在您的 C 等效项中,库开销较少。

如果您的所有包装代码都遵循上述内容,那么就膨胀而言,几乎没有。

在我看来,您的解决方案是一种很好的实现包装器的方法。

【讨论】:

  • 如果你不知道——在 linux 上,size 实用程序会告诉你静态代码大小$ size <your-exe>
  • 我希望我有 linux。不幸的是我被windows困住了(并且赢了xp!)
  • 啊,是的 - 正是我刚刚在评论中写的:-)。让我们看看这是否真的已经完成了......
【解决方案3】:

为了对已经很慢的 Gtk+ 造成最小的性能膨胀,我几乎在所有地方都使用了内联函数

编译器非常擅长知道何时内联以及何时不内联函数。 inline 关键字实际上并不意味着该函数将被内联,只是该函数在不同翻译单元中的定义不会违反单一定义规则 (ODR)。

关于代码大小,它取决于编译器选项和其他一些东西,但是对于您呈现的代码应该没有任何影响,就好像函数被内联一样,对一个函数的调用将是替换为对另一个的调用,那些是一个班轮。请注意,许多编译器确实会创建函数并将其保留在二进制文件中,即使所有使用都已内联,您可能需要查看编译器/链接器文档以了解如何删除它们,但即使创建了它们,项目的大小应该不会受到太大影响。

如果你愿意让你的项目从 6.5kb 增长到 400kb,你应该会更好。

【讨论】:

    【解决方案4】:

    这取决于

    • 你的函数有多大
    • 您使用它们的频率
    • 编译器设置(您可以影响)
    • 编译器实现(你不能)

    等等。等等。换句话说,不要假设,要衡量。使用或不使用内联函数在您的项目中做一个有代表性的部分,看看在您的特定情况下会产生什么效果。您在互联网上收到的任何预测充其量只是有根据的猜测。

    【讨论】:

    • 我不是在寻找预测,我需要建议,我是否应该使用内联函数来构建包装器? 我的意思是为了让提高性能?
    • @burning:答案是:也许。你甚至不知道这个臃肿是从哪里来的……追踪它,可能会有一些具体的建议。
    【解决方案5】:

    如果您的函数只有一两行,那么它们不太可能将生成的二进制文件的大小增加相当大的数量。事实上,如果代码本身小于函数调用的开销代码,他们可以将其缩小。

    请注意,无论哪种方式,开销都可以忽略不计。只需删除对std::sort 的单个调用或std::map 的实例化即可抵消任何膨胀。如果您关心代码大小,那么小内联函数是您最不必担心的。

    【讨论】:

      【解决方案6】:

      您提供的示例在调用方不应导致代码增加。替换一个只执行一次调用而不执行其他任何操作的内联函数,应该由任何体面的编译器进行优化。

      您必须调查真正的增长点在哪里。查看生成的汇编程序,这通常可以很好地了解开销。

      【讨论】:

        【解决方案7】:

        没有简单的答案。这取决于许多因素:

        • 函数的复杂性
        • CPU 架构和内存模型
        • 实现过程调用约定
        • 参数传递机制,包括对象如何访问this

        尺寸并不是唯一需要考虑的效率。在许多现代 CPU 上,执行任何类型的分支或调用指令都可能使 CPU 停顿许多指令的等效时间。通常用函数体中的几条指令替换call 指令是一个很大的时间收益。这也可能是代码大小的优势,因为 CPU 寄存器中可能已经有函数参数,因此不需要推送或移动它们。

        在存在已知的空间或速度问题之前,不要进行小优化。然后寻找影响 90% 问题的 10% 修复。

        【讨论】:

          【解决方案8】:

          恭喜,您正在重新发明GTKmm,这是 GTK+ 的官方 C++ 绑定。

          【讨论】:

          • 我不是在创建一个 binding 我是在创建一个 wrapper 来与 c 代码一起使用,而不是提供一个全新的 c++ 接口。
          • 它给你带来了什么好处? GTKmm 已经提供了 C++ 接口。它会在 GTK 更改时更新,而您必须自己维护包装器。
          • 我的包装器提供了两个主要优点性能,并且它可以与现有的 c 代码一起使用。因为我已经使用 c gtk 有一段时间了,所以让一个库与我以前的 c gtk+ 代码兼容会有所帮助。
          • 然后直接从你的 C++ 代码中调用 C 代码,你将获得最大的性能...反正性能。我真的认为你走错了路,因为:你正在做某种微优化,甚至没有 havig 数字来比较性能(你为什么认为 GTKmm 会很慢?),而你正在做的是反对代码的可重用性和简单性......当某些事情会误导那些在你之后将手放在代码上的人时,你会做你自己的事情......
          猜你喜欢
          • 2022-06-20
          • 2015-11-20
          • 2021-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-14
          • 2012-08-27
          • 2019-07-02
          相关资源
          最近更新 更多