Message-ID: From: "lukaseder (@lukaseder)" To: "pgjdbc/pgjdbc" Date: Thu, 13 Nov 2025 16:40:42 +0000 Subject: [pgjdbc/pgjdbc] issue #3865: Avoid well known slow JDK libraries String.format() and DecimalFormat in PGInterval::getValue to get drastic performance boost List-Id: X-GitHub-Author-Id: 734593 X-GitHub-Author-Login: lukaseder X-GitHub-Issue: 3865 X-GitHub-Repo: pgjdbc/pgjdbc X-GitHub-State: closed X-GitHub-Type: issue X-GitHub-Url: https://github.com/pgjdbc/pgjdbc/issues/3865 Content-Type: text/plain; charset=utf-8 **Describe the issue** `PGInterval::getValue` uses `Stirng::format` and `DecimalFormat` to format the interval value to the PostgreSQL notation: ```java public @Nullable String getValue() { if (isNull) { return null; } DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.US); df.applyPattern("0.0#####"); return String.format( Locale.ROOT, "%d years %d mons %d days %d hours %d mins %s secs", years, months, days, hours, minutes, df.format(getSeconds()) ); } ``` This performs terribly and should be avoided. I'm suggesting this alternative implementation, which should do the same thing (except it always renders trailing zeros): ```java public String getValue() { if (isNull) { return null; } StringBuilder sb = new StringBuilder(); sb.append(years).append(" years ") .append(months).append(" mons ") .append(days).append(" days ") .append(hours).append(" hours ") .append(minutes).append(" mins "); if (wholeSeconds < 0 || microSeconds < 0) sb.append('-'); sb.append(Math.abs(wholeSeconds)); if (microSeconds != 0) { sb.append('.'); String s = "" + Math.abs(microSeconds); for (int i = s.length(); i < 6; i++) sb.append('0'); sb.append(s); } sb.append(" secs"); return sb.toString(); } ``` **Driver Version?** 42.7.8 **Java Version?** openjdk version "21.0.7" 2025-04-15 LTS **OS Version?** Microsoft Windows [Version 10.0.26100.7171] **PostgreSQL Version?** N/A **To Reproduce** Run this JMH benchmark: ```java import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; @Fork(value = 3) @Warmup(iterations = 3, time = 3) @Measurement(iterations = 5, time = 3) public class PGIntervalBenchmark { @State(Scope.Benchmark) public static class BenchmarkState { PGInterval interval; @Setup(Level.Trial) public void setup() throws Exception { interval = new PGInterval(0, 0, 0, 0, 0, 0); } @TearDown(Level.Trial) public void teardown() throws Exception { interval = null; } } @Benchmark public String testGetValue(BenchmarkState state) { return state.interval.getValue(); } } ``` **Expected behaviour** The current version produces these benchmark results: ``` PGIntervalBenchmark.testGetValue thrpt 15 1447069.902 ± 67832.795 ops/s ``` My version produces a 32x improvement: ``` PGIntervalBenchmark.testGetValue thrpt 15 46957485.917 ± 497298.690 ops/s ```