I was trying to process some text files approximately simulating a deterministic finite state automaton. This worked well but the performance was absolutely horrible.
I started investigating and found that the "code size" has a weird and very large effect on the performance. I created a dummy program to illustrate the effect. The following is acceptably fast on my computer (~0.250s):
package com.foo.bar;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
public class DummyFileProcessor {
public static void main(String[] args) throws IOException {
// `args[0]` is the path to the "citylots.json" file from here:
// https://github.com/zemirco/sf-city-lots-json (181 MB).
final long startTime = System.currentTimeMillis();
final short stateAlwaysZero = 0; // always 0 in this demo
try (Reader reader = Files.newBufferedReader(Paths.get(args[0]))) {
final char[] chunk = new char[4096];
for (int chunkLength = reader.read(chunk); chunkLength != -1; chunkLength = reader.read(chunk)) {
for (int i = 0; i < chunkLength; ++i) {
final short triggerAlwaysZero = 0; // always 0 in this demo
switch (stateAlwaysZero) {
case 0: {
switch (triggerAlwaysZero) {
case 0: { break; }
case 1: { break; }
case 2: { break; }
case 3: { break; }
case 4: { break; }
case 5: { break; }
case 6: { break; }
case 7: { break; }
case 8: { break; }
case 9: { break; }
case 10: { break; }
case 11: { break; }
case 12: { break; }
case 13: { break; }
case 14: { break; }
case 15: { break; }
case 16: { break; }
case 17: { break; }
case 18: { break; }
case 19: { break; }
case 20: { break; }
case 21: { break; }
case 22: { break; }
case 23: { break; }
case 24: { break; }
case 25: { break; }
case 26: { break; }
case 27: { break; }
case 28: { break; }
case 29: { break; }
}
break;
}
case 1: { break; }
case 2: { break; }
case 3: { break; }
case 4: { break; }
case 5: { break; }
case 6: { break; }
case 7: { break; }
case 8: { break; }
case 9: { break; }
case 10: { break; }
case 11: { break; }
case 12: { break; }
case 13: { break; }
case 14: { break; }
case 15: { break; }
case 16: { break; }
case 17: { break; }
case 18: { break; }
case 19: { break; }
case 20: { break; }
case 21: { break; }
case 22: { break; }
case 23: { break; }
case 24: { break; }
case 25: { break; }
case 26: { break; }
case 27: { break; }
case 28: { break; }
case 29: { break; }
case 30: { break; }
case 31: { break; }
case 32: { break; }
case 33: { break; }
case 34: { break; }
case 35: { break; }
case 36: { break; }
case 37: { break; }
case 38: { break; }
case 39: { break; }
}
}
}
}
final long endTime = System.currentTimeMillis();
System.out.println("done after " + (((double) (endTime - startTime)) / 1000) + "s");
}
}
Now I just copy the switch (triggerAlwaysZero) { ... }
part into the other cases (1
, 2
, ...) of switch (stateAlwaysZero)
(these cases are never chosen). This makes the code large but it does not have a big impact when done only to the cases 0
to 33
. But when I also paste this switch (triggerAlwaysZero) { ... }
part to case 34
, the processing time suddenly jumps from around 0.250s to around 3.7 seconds.
Does anyone have an idea what is going on here?
$ java -version
openjdk version "11.0.9.1" 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)