【发布时间】:2010-12-05 12:35:47
【问题描述】:
我指的是this discussion。我从未用 C 或 C++ 编写过任何代码。我没有任何CS背景。然而,我作为 Java 开发人员已经工作了 5 年,现在我决定更多地了解 CS 并做一些追赶。
【问题讨论】:
-
来自 C# 世界 stackoverflow.com/questions/648409/inlining-functions 的副本,但我认为这里的答案更好
我指的是this discussion。我从未用 C 或 C++ 编写过任何代码。我没有任何CS背景。然而,我作为 Java 开发人员已经工作了 5 年,现在我决定更多地了解 CS 并做一些追赶。
【问题讨论】:
正如其他答案中已经提到的,内联是有代价的。通常这被认为很小,但是在实际测量时,您可能会感到惊讶并得知它可能比您获得的更大(所以what other people say 是正确的:除非您进行了测量,否则不要优化)。
值得注意的是,在 Linux 内核中,由于成本太高(较大的函数消耗更多的 cpu 内存缓存,并且由此产生的缓存未命中比仅仅调用打算内联的函数)。详情请参阅doc/Documentation/process/coding-style.rst 中的“第 15 章:内联疾病”。
【讨论】:
Norman Maurer 在他的 blog JVM 和 JIT 内联功能中解释了这样的功能
内联是一种技术,它基本上只是将一个方法“内联”到另一个方法中,从而摆脱方法调用。 JIT 会自动检测“热”方法并尝试为您内联它们。如果一个方法被执行超过 X 次,则该方法被认为是“热”,其中 X 是一个阈值,可以使用启动 java 时的 JVM 标志(默认为 10000)。这是必需的,因为内联所有方法会比其他任何方法造成更大的伤害,因为生成的字节码很大。除此之外,当优化在以后的状态下被证明是错误的时,JIT 可能会“恢复”以前的内联代码。请记住,JIT 代表 Just in Time,因此在执行代码时会进行优化(包括内联但也包括其他内容)。
还有警告
但即使 JVM 认为一个方法是“热的”,它也可能不会内联它。但为什么?最可能的原因之一是它内联太大了。
您可以在Eva Andreasson's Java World Post 找到一个用于内联Java 代码的非常简单的代码示例。您可以在下面找到帖子的相关部分。
许多优化都试图消除机器级跳转指令(例如,用于 x86 架构的 JMP)。跳转指令改变指令指针寄存器,从而转移执行流程。相对于其他 ASSEMBLY 指令,这是一项昂贵的操作,这就是为什么它是减少或消除的常见目标。 一种非常有用且广为人知的优化称为内联。由于跳转代价高昂,因此将许多对具有不同入口地址的小方法的频繁调用内联到调用函数中会很有帮助。清单 3 到 5 中的 Java 代码举例说明了内联的好处。
清单 3. 调用者方法
int whenToEvaluateZing(int y) {
return daysLeft(y) + daysLeft(0) + daysLeft(y+1);
}
清单 4. 被调用的方法
int daysLeft(int x){
if (x == 0)
return 0;
else
return x - 1;
}
清单 5. 内联方法
int whenToEvaluateZing(int y){
int temp = 0;
if(y == 0) temp += 0; else temp += y - 1;
if(0 == 0) temp += 0; else temp += 0 - 1;
if(y+1 == 0) temp += 0; else temp += (y + 1) - 1;
return temp;
}
在清单 3 到 5 中,调用方法对 小方法,为了这个例子,我们假设它更多 内联比跳到三倍更有利。
内联调用的方法可能没有太大区别 很少,但内联一个经常出现的所谓“热”方法 调用可能意味着性能上的巨大差异。内联也 经常为进一步优化让路,如清单 6 所示。
清单 6. 内联后,可以应用更多优化
int whenToEvaluateZing(int y){
if(y == 0) return y;
else if (y == -1) return y - 1;
else return y + y - 1;
}
【讨论】:
内联函数通常用于 C++ 头文件而非 Java。 C++ 头文件通常不包含已实现的代码,并且被视为同名 cpp 文件的接口,该文件通常包含已实现的代码。在头文件中包含内联函数是合法的,通常是一个小的轻量级函数。内联函数确实是有代价的,所以它们不应该是大型内存密集型操作。对于小型例程,性能损失很小,它们更多地用于方便。
【讨论】:
在那次讨论中,Jon Skeet 提到了 Client jvm (hotspot) v Server jvm,如果允许 JIT (just-in-time) 编译器带来基于时间的增强,则可以在运行时获得性能改进。这就是Java中的“它是如何完成的”。
最初,没有从很多地方调用的小段代码将被编译器“内联”,这意味着所谓的单例将直接放在指令指针代码路径中,进行函数分支并返回成本比仅仅展开循环或函数调用并将指令“放在那里”更多的处理器能力
今天,Singleton 是多页讨论的主题,循环展开以及内联之类的东西在某种程度上已从其原始上下文中删除。您可以阅读 Dov Bulka 关于此事的非常有见地的工作,以了解 C/C++ 对此事的看法。对于 Java,研究 java.util 中丰富的库比研究内联和深度编译器问题更能满足您的需求 - 您可能会陷入对数据结构的根深蒂固的四面楚歌的内部战争中,这会掩盖对 16 位代码的调用,并且学习曲线没有尽头。
您可以在 Java 中执行 instanceof,它类似于 vf 表(请不要加热),但可以将其视为您一直在使用强类型语言编写 - 现在将使用字符串可能失控的语言编写很容易在它没有业务的地方闲逛。我最近尝试用 C 代码编写用 Java 构建图像的代码。我很快发现自己正在查看 oxr 表以进行强加密——这与我正在编写的代码无关。
如何在 C/C++ 中编写一个字符串类,它有一个用于 32 字节以下字符串的小缓冲区并捕获指针以便它们只对字符串进行操作?
不是想取笑你或任何东西,它只是一个非常好的起点,而不是内联和编译器科学。
【讨论】:
作为 Java 开发人员,您通常不必担心方法内联。 Java 的即时编译器可以并且将在大多数有意义的地方自动完成。
像 eclipse 这样的 IDE 可以有一个功能,允许您在源代码级别内联方法 - 从不 这样做是为了提高性能,只是为了代码的可读性(例如,当您意识到该方法只调用一个其他方法,本身不添加任何有用的东西)。
【讨论】:
编译器优化答案是正确的。不过还有另一种用法——在refactoring 中,内联是指用方法体替换方法调用,然后删除方法。见Inline Method。还有类似的重构,比如Inline Class。
编辑:请注意,重构是手动或使用工具完成的;无论哪种情况,都涉及更改源代码。
【讨论】:
在执行一段给定的代码时,无论何时调用标准函数,执行时间都比将包含在该函数中的代码转储到那里稍长。每次转储函数中包含的整个代码在另一端无法维护,因为它显然会导致代码重复的混乱。
内联通过让您将函数声明为 内联(至少在 C++ 中)解决了性能和可维护性问题,这样当您调用该函数时 - 而不是您的应用程序在运行时跳转 - 每次调用给定函数时,内联函数中的代码都会在编译时注入。
这样做的缺点是 - 如果你内联你多次调用的大函数 - 你的程序的大小可能会显着增加(最佳实践建议确实只在小函数上这样做) .
【讨论】:
内联是指编译时优化,其中一小段代码将被注入到调用函数中,而不需要单独调用。
【讨论】:
基本上,在 C/C++ 中,编译器可以内联函数,这意味着不是调用函数来执行该操作,而是将代码添加到调用函数的块中,所以就好像它从来没有是一个单独的函数调用。
这将更详细地介绍: http://www.codersource.net/cpp_tutorial_inline_functions.html
【讨论】:
http://en.wikipedia.org/wiki/Inlining
在计算中,内联扩展或内联是一种编译器优化,它用被调用者的主体替换函数调用站点。这种优化可能会提高运行时的时间和空间使用率,但可能会增加最终程序的大小。
【讨论】: