随着时间的发展, 现在的虚拟机技术越来越成熟了, 在有些情况下, Java,.Net 等虚拟机密集计算的性能已经和 C++ 相仿, 在个别情况下, 甚至还要更加优秀. 本文详细分析几个性能测试案例, 探讨现象背后的原因.
来看两个简单的测试用例. 如下图所示, 均是循环 5000 次, 操作 len = 1000000 的连续内存, 计算执行时间. 左侧为 test1, 右侧为 test2.
类似的程序在 .net core 3.0 Preview6 下测试.
测试结果对比如下:
我们可以看见, 对于 test1,C++ 版本要快很多, 对于 test2,C# 版本和 C++ 版本性能相当, 甚至略快.
为什么会出现这种现象呢? 下面来具体分析:
test1 的循环的赋值是位置无关的, 因此, 编译器可以通过 SIMD 等并行计算指令来优化, test2 的循环的赋值是位置相关的, 编译器很难使用 SIMD 等并行计算指令来优化. 通过上面的结果可以猜测, VC 编译器, 对 test1 进行了并行优化, 而. net core 3.0 preview6 没有对 test1 进行并行优化.
我们来验证这一猜测..net core 3.0 提供了对 SIMD 指令的支持, 下面手动对 test1 进行并行优化, 测试性能:
结果是 0.633s, 接近于 C++ 版本的 0.441s. 相对于优化前的 2.289s, 提速了 3 倍多.
同样的程序, 我用 java 8 测试, 结果大吃一惊:
test1 耗时 0.654s, 和并行优化后的. net core 近似, 可见 jvm 虚拟机对此进行了并行优化. test2 耗时 1.755s, 比 C++ 版本和. net core 版本都要快, 并且差距巨大!
显然, jvm 对 test2 这种情况进行了特殊关照. 要理解这一现象, 就需要对 Java 虚拟机的机制有深入了解. HotSpot 虚拟机里内置了两个 JIT 编译器: Client Compiler 和 Server Compiler, 简称为 C1 编译器和 C2 编译器. C1 编译器将字节码编译为本地代码, 进行简单, 可靠的优化, 如有必要将加入性能监控的逻辑. C2 编译会启用一些编译耗时较长的优化, 甚至进行一些激进优化.
查找文献可知, 默认情况下, 当方法调用次数 + 循环回边次数超过 10000, 计数器是 int 等几个简单类型, 步增是常量时, 会触发 C2 编译优化. test2 恰恰满足这三种情况!
下面我们再设计一个实验, 将步增改为变量, 看看测试结果:
由测试可知, 将步增改为变量后, 测试结果为 6.163 秒, 和 C++ 及 .net core 测试结果近似.
针对这个测试案例, 可以猜测 C2 优化时进行了循环展开. 下面, 我们在 .net core 下手动展开循环, 测试性能, 验证我们的猜想:
测试结果为 1.983s, 近似 java8 的 1.755s. 猜想得到验证.
----
总结: 随着 JVM,.Net 等虚拟机技术的发展, 语言特性对高性能计算性能影响越来越低, 对计算机体系结构, 编译原理, 虚拟机编译机制的理解, 对性能的影响变得更为重要. JVM 的自动优化做的非常的强悍,.net core 在这方面还有不小差距, 不过 .net core 可以通过手工优化来弥补这一差距.
来源: https://www.cnblogs.com/xiaotie/p/perf-3langs.html