因此,我们都知道,Java解释缓慢且C的编译和优化运行速度非常快。 您可能知道,图片截然不同。
TL; DR Java适用于星座,它的速度更快,在JIT上可以执行内联,因为所有方法/功能都是可见的,而C编译器无法在编译单元(例如库等)上执行优化。
AC编译器将C代码作为输入,对其进行编译和优化,并生成要执行的特定CPU或体系结构的机器代码。 这导致可执行文件,无需进一步步骤即可直接在给定计算机上运行。 另一方面,Java有一个中间步骤:字节码。 因此,Java编译器将Java代码作为输入并生成字节码,而字节码基本上是抽象机的机器代码。 现在,对于每个(流行的)CPU架构,都有一个Java Virual机器,该机器模拟此抽象机器并执行(解释)生成的字节码。 这听起来很慢。 但是另一方面,字节码是可移植的,因为相同的输出将在所有平台上运行,因此口号“ 一次写入,随处运行 ”。
现在,使用上述方法,它会变得“ 写一次,到处等待 ”,因为解释器会很慢。 因此,现代JVM要做的就是及时编译。 这意味着JVM在内部将字节码转换为用于CPU的机器代码。 但是由于此过程非常复杂,因此Hotspot JVM (最常用的一种)仅对经常执行的代码片段执行此操作(因此命名为Hotspot )。 除了更快地启动(解释器立即启动,JIT编译器根据需要启动)之外,还有另一个好处:热点JIT已经知道经常调用什么部分,什么不经常调用-因此它可以在优化输出时使用它–这就是我们的例子发挥作用的地方。
现在,在查看我的完整示例之前,请注意,Java具有许多功能,例如动态调度(在接口上调用方法),它还带有运行时开销。 因此,Java代码可能更容易编写,但通常仍会比C代码慢。 但是,当涉及纯数字运算时,就像下面的示例一样,有一些有趣的发现。
因此,无需进一步讨论,这是示例C代码:
test.c:
int compute(int i);int test(int i);int main(int argc, char** argv) {int sum = 0;for(int l = 0; l < 1000; l++) {int i = 0;while(i < 2000000) {if (test(i))sum += compute(i);i++;} }return sum;
}
test1.c:
int compute(int i) {return i + 1;
}int test(int i) {return i % 3;
}
现在,主要功能的实际计算完全不重要。 关键是它经常调用两个函数(测试和计算),并且这些函数在另一个编译单元(test1.c)中。 现在让我们编译并运行程序:
> gcc -O2 -c test1.c> gcc -O2 -c test.c> gcc test.o test1.o> time ./a.outreal 0m6.693s
user 0m6.674s
sys 0m0.012s
因此,此过程大约需要6.6秒 。 现在让我们看一下Java程序:
Test.java
public class Test {private static int test(int i) {return i % 3; }private static int compute(int i) {return i + 1; }private static int exec() {int sum = 0; for (int l = 0; l < 1000; l++) {int i = 0; while (i < 2000000) {if (test(i) != 0) {sum += compute(i); }i++; }}return sum; }public static void main(String[] args) {exec(); }
}
现在让我们编译并执行以下命令:
> javac Test.java> time java Testreal 0m3.411s
user 0m3.395s
sys 0m0.030s
因此,花费3.4秒的时间 ,Java可以轻松完成此简单任务(甚至包括JVM的缓慢启动)。 问题是为什么? 当然,答案是JIT可以执行C编译器无法执行的代码优化。 在我们的例子中是函数内联。 当我们在自己的编译单元中定义了两个微型函数时,编译器无法在编译test.c时内联这些函数。另一方面,JIT拥有所有方法,并且可以执行主动内联,因此编译后的代码速度更快。
那么,这是一个在现实生活中从未发生过的完全异国情调的虚构例子吗? 是的,没有。 当然,这是一个极端的情况,但是请考虑一下代码中包含的所有库。 所有这些方法都不能在C语言中进行优化,而在Java中,字节码的来源无关紧要。 由于所有JIT都存在于正在运行的JVM中,因此JIT可以对其核心内容进行优化。 当然,C语言有一个卑鄙的技巧可以减轻这种痛苦:Marcos。 在我看来,这就是市长的原因之一,为什么C中如此之多的库仍然使用宏而不是适当的功能-伴随着它们带来的所有问题和麻烦。
现在就在火焰战争开始之前:这两种语言都有其长处和短处,并且在软件工程领域都占有一席之地。 撰写这篇文章仅是为了吸引您的注意,并想知道现代JVM每天都在发生。
翻译自: https://www.javacodegeeks.com/2016/02/c-code-always-runs-way-faster-java-right-wrong.html