一般的
在您的常规编译器中,生成的代码通常是相同的,至少在假设您使用常规时
csc.exe /optimize+
cl.exe /O2
g++ -O2
以及相关的默认优化模式。
一般的口头禅是:配置文件,配置文件,配置文件(并且在您的分析器告诉您之前不要进行微优化)。大家可以随时查看生成的代码2,看看是否还有改进的余地。
这样想,例如 C# 代码:
C#/.NET
您的每个complexExpressions都是一个事实上的函数调用调用(call、calli、callvirt opcode 3),需要将其参数推入堆栈。返回值将被压入堆栈,而不是退出时的参数。
现在,CLR 是一个基于堆栈的虚拟机(即无寄存器),这与堆栈上的匿名临时变量完全相同。唯一的区别是代码中使用的标识符数量。
现在 JIT 引擎的作用是另一回事:JIT 引擎必须将这些调用转换为本地汇编,并且可能通过调整寄存器分配、指令顺序、分支预测和类似的东西来进行优化1
1(尽管在实践中,对于这个示例,它不会被允许做更有趣的优化,因为它complex function calls
可能有副作用,而且 C# 规范对评估顺序和所谓的序列非常清楚)点。但是请注意,允许 JIT 引擎内联函数调用,以减少调用开销。
不仅当它们是非虚拟的,而且(IIRC)当运行时类型可以在某些 .NET 框架内部的编译时静态地知道时。我必须为此查找参考,但实际上我认为 .NET 框架 4.0 中引入了一些属性来明确防止框架函数的内联;这样微软就可以在服务包/更新中修补库代码,即使用户程序集已提前编译 (ngen.exe) 为本机映像。
C/C++
在 C/C++ 中,内存模型要宽松得多(即至少在 C++11 之前),代码通常在编译时直接编译为本机指令。补充一点,C/C++ 编译器通常会进行积极的内联,即使在这种编译器中的代码通常也是一样的,除非你在没有启用优化的情况下进行编译
2我用
ildasm
或monodis
查看生成的 IL 代码
mono -aot=full,static
或mkbundle
生成本机对象模块并objdump -CdS
查看带注释的本机汇编指令。
请注意,这纯粹是出于好奇,因为我很少会以这种方式发现有趣的瓶颈。但是,请参阅 J on Skeet 的关于性能优化的博客文章,Noda.NET
以了解可能潜伏在生成的通用类 IL 代码中的惊喜的好例子。
3对于编译器内在函数上的运算符, 编辑不准确,即使它们只会将结果留在堆栈上。