<div dir="ltr">Hi,<br><br>today I stumbled upon a performance issue with the Long.compress/expand and Integer.compress/expand intrinsics on certain AMD processors. I discovered this while working on an optimized varint decoder where I was hoping to use Long.compress() to speed up bit extraction. Instead, I found my "optimized" version was slower than my naive loop-based implementation. After some digging, I believe I understand what's happening.<br><br>**Background**<br><br>The compress and expand methods (added in JDK 19 via JDK-8283893 [1]) are 

intrinsified 

by C2 to use the BMI2 PEXT and PDEP instructions when the CPU reports BMI2 support. <br>This works great on Intel Haswell+ and AMD Zen 3+, where these instructions execute in dedicated hardware with approximately 3-cycle latency.<br>However, AMD processors from Excavator before Zen 3 implement PEXT/PDEP via microcode emulation rather than native hardware. <br>This is confirmed by AMD's Software Optimization Guide for Family 19h Processors [2], Section 2.10.2, which states that Zen 3 has native ALU support for these instructions.<br>Wikipedia's page on x86 Bit Manipulation Instruction Sets [3] also documents this behavior:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">AMD processors before Zen 3 that implement PDEP and PEXT do so in microcode, with a latency of 18 cycles rather than (Zen 3) 3 cycles. As a result it is often faster to use other instructions on these processors.</blockquote><br>**Reproducer**<br><br>Here is a JMH benchmark that demonstrates the issue by comparing the intrinsified path against the software fallback using ControlIntrinsic flags:<br><br>```<br>import org.openjdk.jmh.annotations.*;<br><br>import java.util.concurrent.ThreadLocalRandom;<br>import java.util.concurrent.TimeUnit;<br><br>@BenchmarkMode(Mode.AverageTime)<br>@OutputTimeUnit(TimeUnit.NANOSECONDS)<br>@Warmup(iterations = 5, time = 1)<br>@Measurement(iterations = 5, time = 1)<br>@State(Scope.Benchmark)<br>public class PextPdepPerformanceBug {<br>    // I'm not using constants to prevent constant folding<br>    private long longValue;<br>    private long longMask;<br>    private int intValue;<br>    private int intMask;<br><br>    @Setup(Level.Iteration)<br>    public void setup() {<br>        var rng = ThreadLocalRandom.current();<br>        longValue = rng.nextLong();<br>        longMask = rng.nextLong();<br>        intValue = rng.nextInt();<br>        intMask = rng.nextInt();<br>    }<br><br>    // Long.compress (PEXT 64-bit)<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=-_compress_l",<br>        "-Xcomp"<br>    })<br>    public long compressLongSoftware() {<br>        return Long.compress(longValue, longMask);<br>    }<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=+_compress_l",<br>        "-Xcomp"<br>    })<br>    public long compressLongIntrinsic() {<br>        return Long.compress(longValue, longMask);<br>    }<br><br>    // Long.expand (PDEP 64-bit)<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=-_expand_l",<br>        "-Xcomp"<br>    })<br>    public long expandLongSoftware() {<br>        return Long.expand(longValue, longMask);<br>    }<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=+_expand_l",<br>        "-Xcomp"<br>    })<br>    public long expandLongIntrinsic() {<br>        return Long.expand(longValue, longMask);<br>    }<br><br>    // Integer.compress (PEXT 32-bit)<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=-_compress_i",<br>        "-Xcomp"<br>    })<br>    public int compressIntSoftware() {<br>        return Integer.compress(intValue, intMask);<br>    }<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=+_compress_i",<br>        "-Xcomp"<br>    })<br>    public int compressIntIntrinsic() {<br>        return Integer.compress(intValue, intMask);<br>    }<br><br>    // Integer.expand (PDEP 32-bit)<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=-_expand_i",<br>        "-Xcomp"<br>    })<br>    public int expandIntSoftware() {<br>        return Integer.expand(intValue, intMask);<br>    }<br><br>    @Benchmark<br>    @Fork(value = 2, jvmArgsAppend = {<br>        "-XX:+UnlockDiagnosticVMOptions",<br>        "-XX:ControlIntrinsic=+_expand_i",<br>        "-Xcomp"<br>    })<br>    public int expandIntIntrinsic() {<br>        return Integer.expand(intValue, intMask);<br>    }<br>}<br>```<br><br>Here are the results on an i7 9700K, which supports the BMI2 instruction set and is not affected by this issue:<br>```<br>Benchmark                                     Mode  Cnt   Score   Error  Units<br>PextPdepPerformanceBug.compressIntIntrinsic   avgt   10   0,545 ± 0,002  ns/op<br>PextPdepPerformanceBug.compressIntSoftware    avgt   10  11,357 ± 0,033  ns/op<br>PextPdepPerformanceBug.compressLongIntrinsic  avgt   10   0,552 ± 0,012  ns/op<br>PextPdepPerformanceBug.compressLongSoftware   avgt   10  16,197 ± 0,203  ns/op<br>PextPdepPerformanceBug.expandIntIntrinsic     avgt   10   0,546 ± 0,006  ns/op<br>PextPdepPerformanceBug.expandIntSoftware      avgt   10  12,179 ± 0,457  ns/op<br>PextPdepPerformanceBug.expandLongIntrinsic    avgt   10   0,548 ± 0,018  ns/op<br>PextPdepPerformanceBug.expandLongSoftware     avgt   10  17,658 ± 0,534  ns/op<br>```<br><br>And here are the results on a Ryzen 7 2700, which supports the BMI2 instruction set. but is also affected by this issue:<br>```<br>Benchmark                                     Mode  Cnt   Score    Error  Units<br>PextPdepPerformanceBug.compressIntIntrinsic   avgt   10  28.010 ±  9.929  ns/op<br>PextPdepPerformanceBug.compressIntSoftware    avgt   10  20.008 ±  2.129  ns/op<br>PextPdepPerformanceBug.compressLongIntrinsic  avgt   10  48.999 ±  8.468  ns/op<br>PextPdepPerformanceBug.compressLongSoftware   avgt   10  28.638 ±  5.336  ns/op<br>PextPdepPerformanceBug.expandIntIntrinsic     avgt   10  24.860 ±  6.784  ns/op<br>PextPdepPerformanceBug.expandIntSoftware      avgt   10  19.277 ±  1.719  ns/op<br>PextPdepPerformanceBug.expandLongIntrinsic    avgt   10  43.889 ± 10.575  ns/op<br>PextPdepPerformanceBug.expandLongSoftware     avgt   10  27.350 ±  1.898  ns/op<br>```<br><br>**Precedent and Scope**<br><br>A similar issue was reported in JDK-8334474 [4], where the compress/expand intrinsics were disabled on RISC-V because the vectorized implementation caused regressions compared to the pure-Java fallback.<br>This led me to investigate whether other JDK intrinsics relying on BMI2 instructions might be affected.<br>The good news is that, as stated before, PEXT and PDEP are the only BMI2 instructions that AMD implemented via microcode on pre-Zen 3 processors: the others execute efficiently on all BMI2-capable hardware.<br>I also verified that no other JDK methods use PEXT/PDEP, so the four methods covered in this report (Long.compress, Long.expand, Integer.compress, Integer.expand) should be the only ones affected.<br>It's worth verifying this though as the JDK is very large and I could have missed such examples.<br><br>**Mitigation**<br><br>The intrinsic selection logic should check both BMI2 support and CPU vendor/family. <br>Specifically, disable these intrinsics when the CPU vendor is AMD and the family is less than 0x19 (Zen 3). <br>I think this could be implemented in <a href="http://x86.ad">x86.ad</a> [5], alongside the existing BMI2 check, but I'm not familiar with C2's source code.<br>Still, I would be happy to work on this issue myself if the issue is verified and it's acceptable for me to work on it.<br><br>Thanks for reading!<br><br>[1] <a href="https://bugs.openjdk.org/browse/JDK-8283893">https://bugs.openjdk.org/browse/JDK-8283893</a><br>[2] <a href="https://developer.amd.com/resources/developer-guides-manuals/">https://developer.amd.com/resources/developer-guides-manuals/</a><br>[3] <a href="https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set">https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set</a><br>[4] <a href="https://bugs.openjdk.org/browse/JDK-8334474">https://bugs.openjdk.org/browse/JDK-8334474</a><br>[5] <a href="https://github.com/jatin-bhateja/jdk/blob/7d35a283cf2497565d230e3d5426f563f7e5870d/src/hotspot/cpu/x86/x86.ad#L3183">https://github.com/jatin-bhateja/jdk/blob/7d35a283cf2497565d230e3d5426f563f7e5870d/src/hotspot/cpu/x86/x86.ad#L3183</a></div>