内联是什么?

41

我是在参考这个讨论。我从未编写过C或C++代码,也没有任何计算机科学背景。但我已经作为Java开发人员工作了5年,现在决定学习更多关于计算机科学的知识,并做一些追赶。


一个来自C#世界的重复问题https://dev59.com/m0bRa4cB1Zd3GeqP4uMX,但我认为这里的答案更好。 - nawfal
10个回答

62

执行某段代码时,每当调用标准函数时,执行时间都会略微增加,而将该函数中的代码直接放入其中则无法维护,因为这显然会导致代码重复混乱。

内联通过让您将函数声明为内联(至少在C ++中),从而解决了性能和可维护性问题,因此当您调用该函数时,而不是在运行时跳转,编译时会将内联函数中的代码注入到每次调用该函数时。

缺点是,如果对您经常调用的大型函数进行内联,则程序的大小可能会显着增加(最佳实践确实建议仅在小型函数上使用)。


21

http://zh.wikipedia.org/wiki/内联函数

内联展开,或称为内联,是一种编译器优化技术,它将函数调用点替换为被调用函数的函数体。这种优化可能会在运行时改善时间和空间使用情况,但可能会增加最终程序的大小。


10
作为Java开发人员,通常无需担心方法内联。在大多数适合的情况下,Java的即时编译器都可以自动完成此操作。
像Eclipse这样的IDE可以具有一项功能,允许您在源代码级别内联方法。但是请不要仅仅出于性能原因而使用该功能,仅应用于代码可读性(例如当您意识到该方法仅调用一个其他方法而没有添加任何有用信息时)。

作为一名Java开发人员,我从来不需要担心这个问题。但是我正在努力学习,只是想知道什么是ininling。 - Roger

4
Norman Maurer在他的博客中解释了JVM和JIT的内联功能,就像这样:
内联是一种技术,基本上只是将一个方法嵌入到另一个方法中,从而消除方法调用。JIT会自动检测“热门”方法,并尝试为您进行内联。如果一个方法被执行超过X次(X是可以在启动Java时使用JVM标志配置的阈值,默认值为10000),那么该方法被视为“热门”。这是必需的,因为内联所有方法会产生巨大的字节码,会造成更多的伤害而不是好处。此外,当优化结果在后期被证明是错误的时候,JIT可能会“恢复”以前内联的代码。请记住,JIT代表即时,所以在执行代码时进行优化(包括内联但也包括其他内容)。
但同时也有一个警告:
但是,即使JVM认为某个方法很“热门”,它也可能不进行内联。但为什么?最有可能的原因之一是它太大而无法内联。
您可以在Eva Andreasson的Java World Post中找到一个非常简单的代码示例来内联Java代码。相关部分如下所示:
许多优化尝试消除机器级跳转指令(例如x86体系结构的JMP)。跳转指令更改指令指针寄存器,从而传输执行流。相对于其他汇编指令,这是一项昂贵的操作,这就是为什么它是减少或消除的常见目标。一个非常有用和众所周知的优化目标是称为内联。由于跳转很昂贵,因此将许多频繁调用具有不同入口地址的小方法内联到调用函数中可能会有所帮助。列表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; 
}

在第三到第五个示例中,调用方法会调用一个小方法三次,我们假设为了本例更有益于内联而不是跳转三次。
内联一个很少被调用的方法可能没有多大的差别,但内联一个所谓的“热门”方法,即经常被调用的方法,可能会对性能产生巨大影响。内联还经常为进一步优化让路,如列表6所示。 列表6. 在内联后,可以应用更多优化
int whenToEvaluateZing(int y){
   if(y == 0) return y;
   else if (y == -1) return y - 1;
   else return y + y - 1;
}

3

正如其他答案中已经提到的那样,内联有一定的代价。通常认为这个代价很小,但是实际测量时可能会发现代价比你所获得的更大(所以其他人说的是正确的:不要优化除非你已经测量过了)。

值得注意的是,在Linux内核中,他们在一段时间前开始取消原本内联的函数,因为代价太高(较大的函数会占用更多的CPU内存缓存,导致缓存失效的代价比调用原本应该内联的函数更高)。详情请参见doc/Documentation/process/coding-style.rst的“第15章:内联病”。


1
好的观点和一个很好的例子,说明硬件发展如何影响(在这种情况下:使无效)软件技术——内存带宽无法跟上CPU速度,因此任何试图通过牺牲内存消耗来换取CPU周期的技术可能会适得其反。 - Michael Borgwardt
是的,很好的观点。这也是编译器设计和API让我困惑的地方——如果那些对机械工作没有直觉把“小而紧凑的代码”带入设计,我们到底获得了什么?计算设备只是计算机械,也就是简单的机器——没有其他东西。所以,如果通过混淆来保证安全性是受欢迎的,我们可以通过大量的设计讨论来证明细节是肮脏的想法。 - Nicholas Jordan

1
编译器优化的答案是正确的。然而,还有另一种用法 - 在重构中,内联指的是用方法体替换方法调用,然后删除该方法。请参见内联方法。还有类似的重构,例如内联类
编辑:请注意,重构是手动或使用工具完成的;在任何情况下都涉及更改源代码。

0
基本上,在C/C++中,编译器可以内联函数,这意味着代码不是通过函数调用来执行操作,而是将代码添加到调用函数的块中,因此就好像它从未是一个单独的函数调用一样。
这里有更详细的解释: http://www.codersource.net/cpp_tutorial_inline_functions.html

0
内联是指编译时优化,其中一小段代码函数将被注入到调用函数中,而不需要单独调用。

0

内联函数通常用于C++头文件而不是Java。C++头文件通常不包含已实现的代码,并被认为是同名cpp文件的接口,该cpp文件通常包含实现的代码。在头文件中包含内联函数是合法的,通常是一些小型轻量级的函数。内联函数确实会有一些成本,因此它们不应该是大型内存密集型操作。对于小型例程,性能损失很小,更多地用于方便性。


-1
在那次讨论中,Jon Skeet提到了客户端JVM(热点)与服务器端JVM之间的性能改进,如果允许JIT(即时编译器)进行基于时间的增强,则可以在运行时获得性能提升。这就是Java中的“做法”。
最初,编译器会将未从多个位置调用的小代码段“内联”,这意味着所谓的单例将直接放置在指令指针代码路径中,执行函数分支和返回成本比展开循环或函数调用并将指令“放在那里”更耗费处理器功率。
如今,单例是多页讨论的主题,展开循环以及类似内联的东西已经与它们最初的上下文有些脱离了。您可以阅读Dov Bulka的非常有见地的作品来了解C/C++对此问题的看法。对于Java而言,研究其丰富的java.util库将更好地满足您的需求,而不是研究内联和深度编译器问题——您可能会陷入关于数据结构的根深蒂固的内部战争中,这些问题掩盖了对16位代码的调用,并且在您的学习曲线上没有尽头。

在Java中,您可以使用instanceof,它类似于vf-table(请不要激动,但是请想象一下您一直在编写强类型语言,现在将要编写的是一个字符串可以轻松地逃脱并在没有业务的情况下四处探索的语言。最近我尝试从C代码中构建Java中的图像。很快我发现自己正在查看用于强加密的oxr表-这与我正在编写的代码无关。

您会如何在C / C ++中编写一个字符串类,该类具有小于32个字节的字符串的小缓冲区,并捕获指针,使其仅对字符串进行操作?

我并不是要挑逗你或者什么的,只是这是一个非常好的起点,而不是内联和编译器科学。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接