CPU的div指令和HotSpot的JIT代码之间存在较大的性能差距

从CPU的开始,一般知道整数除法指令是昂贵的。 我看到今天有多糟糕的CPU,它拥有数十亿晶体管的奢侈品。 我发现硬件idiv指令对于常量除数的性能仍然比JIT编译器能够发出的代码差得多,后者不包含idiv指令。

为了在专用的微基准测试中实现这一点,我写了以下内容:

 @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @OperationsPerInvocation(MeasureDiv.ARRAY_SIZE) @Warmup(iterations = 8, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @State(Scope.Thread) @Fork(1) public class MeasureDiv { public static final int ARRAY_SIZE = 128; public static final long DIVIDEND_BASE = 239520948509234807L; static final int DIVISOR = 10; final long[] input = new long[ARRAY_SIZE]; @Setup(Level.Iteration) public void setup() { for (int i = 0; i < input.length; i++) { input[i] = DIVISOR; } } @Benchmark public long divVar() { long sum = 0; for (int i = 0; i < ARRAY_SIZE; i++) { final long in = input[i]; final long dividend = DIVIDEND_BASE + i; final long divisor = in; final long quotient = dividend / divisor; sum += quotient; } return sum; } @Benchmark public long divConst() { long sum = 0; for (int i = 0; i < ARRAY_SIZE; i++) { final long in = input[i]; final long dividend = DIVIDEND_BASE + in; final int divisor = DIVISOR; final long quotient = dividend / divisor; sum += quotient; } return sum; } } 

简而言之,除了一个( divVar )通过读取数组的数字执行除法而另一个除以编译时常量之外,我在每个方面都有两个相同的方法。 结果如下:

 Benchmark Mode Cnt Score Error Units MeasureDiv.divConst avgt 5 1.228 ± 0.032 ns/op MeasureDiv.divVar avgt 5 8.913 ± 0.192 ns/op 

性能比非常特别。 我的期望是现代英特尔处理器有足够的空间,而且它的工程师有足够的兴趣,在硬件中实现复杂但高性能的划分算法。 然而,JIT编译器通过向其发送一些执行相同工作的其他指令流来击败英特尔,仅快七倍。 如果有的话,专用微码应该能够比JIT通过汇编指令的公共API更好地利用CPU。

为什么idiv仍然这么慢,什么是基本限制?

我们想到的一个解释是假设存在一种除法算法,这种算法首次将红利放入过程中。 然后,JIT编译器将有一个良好的开端,因为它将评估在编译时仅涉及除数的第一部分,并仅将算法的第二部分作为运行时代码发出。 这个假设是真的吗?

正如用户pvg通过评论所解释的那样,假设算法确实存在并且是当前已知的最佳算法。 该算法涉及在预备步骤中由相同的除数进行除法,因此从根本上说它在整体上是不可简化的。 它将在经典出版物Hacker’s Delight的第10章中介绍。