ADDITIONAL SYSTEM INFORMATION :
32-bit armv7 (Zynq 7020)
A DESCRIPTION OF THE PROBLEM :
The first time string concatenation is performed with a sufficiently complex set of operands (e.g. multiple floating point concatenations), it takes a significant amount of time. Later calls are much faster, but the inconsistent performance of the first call is an issue in real-time scenarios on embedded platforms. In testing, the initial call can take 100s of ms, while later calls take a fraction of the time, while a manual StringBuilder approach only takes 20 ms.
It was possible to work around this before JDK-8245455 was merged, because BC_SB could be selected as an alternative string concatenation strategy, and BC_SB has much better performance on the first invocation, and no worse performance on later invocations. While the performance of the default string concatenation strategy has improved substantially from JDK 11 to JDK 17, the performance of it in JDK 17 is still insufficient for real-time applications (on first time invoke).
REGRESSION : Last worked in version 14
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case on a slower platform, particularly an embedded platform.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Running this on Java 11 with -Djava.lang.invoke.stringConcat=BC_SB on the above platform results in:
Don't slow me down, m_a: 1.0, m_b: 2.0, m_c: 3.0, m_d: 4.0, m_e: 5.0, m_f: 6.0, m_g: 7.0, m_h: 8.0, m_i: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 3.0, m_b: 4.0, m_b: 5.0, m_b: 6.0, m_b: 7.0, m_b: 8.0, m_b: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 3.0, m_b: 4.0, m_b: 5.0, m_b: 6.0, m_b: 7.0, m_b: 8.0, m_b: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 2.0, m_b: 4.0, m_b: 5.5, m_b: 4.2, m_b: 7.0, m_b: 8.0, m_b: 9.0
fast time: 0.019999980926513672
slow time: 0.029999971389770508
slow time (again): 9.999275207519531E-4
slow time (after changing): 9.999275207519531E-4
Notice the very similar times between fast time and slow time. The first "slow time" call takes about 50% longer than the manual string builder, and has reasonable performance after that.
ACTUAL -
Running this on Java 17 with the default string concat strategy on the same platform results in:
Don't slow me down, m_a: 1.0, m_b: 2.0, m_c: 3.0, m_d: 4.0, m_e: 5.0, m_f: 6.0, m_g: 7.0, m_h: 8.0, m_i: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 3.0, m_b: 4.0, m_b: 5.0, m_b: 6.0, m_b: 7.0, m_b: 8.0, m_b: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 3.0, m_b: 4.0, m_b: 5.0, m_b: 6.0, m_b: 7.0, m_b: 8.0, m_b: 9.0
Slow me down, m_a: 1.0, m_b: 2.0, m_b: 2.0, m_b: 4.0, m_b: 5.5, m_b: 4.2, m_b: 7.0, m_b: 8.0, m_b: 9.0
fast time: 0.017999887466430664
slow time: 0.23000001907348633
slow time (again): 0.0
slow time (after changing): 9.999275207519531E-4
Notice the very poor "slow time" performance (over 230 ms vs 30 ms in the Java 11 BC_SB case). While this is a much better result than Java 11 (which had "slow time" of over 750 ms!), but it is still an order of magnitude worse than the BC_SB strategy, and the performance on later calls is basically the same.
---------- BEGIN SOURCE ----------
public class Slow {
private static double m_a = 1.0;
private static double m_b = 2.0;
private static double m_c = 3.0;
private static double m_d = 4.0;
private static double m_e = 5.0;
private static double m_f = 6.0;
private static double m_g = 7.0;
private static double m_h = 8.0;
private static double m_i = 9.0;
private static String m_output;
public static double slowString() {
double start = System.currentTimeMillis() / 1000.0;
String fred;
fred = "Slow me down" + ", m_a: " + m_a + ", m_b: " + m_b + ", m_b: " + m_c + ", m_b: " + m_d + ", m_b: " + m_e
+ ", m_b: " + m_f + ", m_b: " + m_g + ", m_b: " + m_h + ", m_b: " + m_i;
double end = System.currentTimeMillis() / 1000.0;
m_output += fred + "\n";
return end - start;
}
public static double fastString() {
double start = System.currentTimeMillis() / 1000.0;
StringBuilder joe = new StringBuilder(256);
joe.append("Don't slow me down");
joe.append(", m_a: ").append(m_a);
joe.append(", m_b: ").append(m_b);
joe.append(", m_c: ").append(m_c);
joe.append(", m_d: ").append(m_d);
joe.append(", m_e: ").append(m_e);
joe.append(", m_f: ").append(m_f);
joe.append(", m_g: ").append(m_g);
joe.append(", m_h: ").append(m_h);
joe.append(", m_i: ").append(m_i);
double end = System.currentTimeMillis() / 1000.0;
m_output += joe.toString() + "\n";
return end - start;
}
public static void main(String[] args) {
m_output = "";
double f = fastString();
double t = slowString();
double t2 = slowString();
m_e = 5.5;
m_f = 4.2;
m_c = 2.0;
double t3 = slowString();
m_output += " fast time: " + f + "\n";
m_output += " slow time: " + t + "\n";
m_output += " slow time (again): " + t2 + "\n";
m_output += " slow time (after changing): " + t3 + "\n";
System.out.println(m_output);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Prior to JDK-8245455, -Djava.lang.invoke.stringConcat=BC_SB could be used as a workaround. Since that has been merged, there is no workaround.
FREQUENCY : always