Loading...
Abstract:
In our next puzzle, we up the ante a bit. We prevent GC during the test() method by storing a strong reference to all our Vectors. Cock and bull story, or are we struggling to identity our biases?
Welcome to the 281st edition of The Java(tm) Specialists' Newsletter. Yes, I'm still in Crete, where the sun is shining and the cicadas are finally starting to emerge from the ground. In a few days time we will have their deafening song make all human communication impossible. But it's the sound of summer, and that's a good time of year :-)
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
A special thank you to all my wonderful Java Specialist readers for sending me a lot of great explanations to yesterday's puzzle. Most answers were correct, although there was one subtlety that so far everyone has missed. Here is a follow-up puzzle, but be careful as our identity should not be in our biases, yet it is. Or is it the other way round? Have fun :-)
In our VectorBench2, instead of only storing the Vector inside the ThreadLocal, we also store it inside a shared set. Since Vector does not implement Comparable, we cannot use TreeSet nor ConcurrentSkipListSet. Also, the hashCode() method of Vector is calculated on all the elements and similarly equals() compares the contents. Since a Vector may change, it is not a good idea to use that as a key in any hash set. In our particular case, all the Vectors contain the same elements, thus only one of them would remain in the Set. Neither HashSet nor ConcurrentHashMap.newKeySet() are appropriate sets for our use case. The only set that would work is the IdentityHashMap converted with Collections.newSetFromMap() to a Set. The IdentityHashMap is not thread-safe, but we can solve that problem by wrapping it with Collections.synchronizedMap(). Since we now hold a reference to the Vector until we leave the method, we can be sure that it cannot be somehow garbage collected early. (My dad would have called all this a cock and bull story, but I assure you that it does affect the outcome.)
import java.util.*; import java.util.stream.*; public class VectorBench2 { public static void main(String... args) { for (int i = 0; i < 10; i++) { test(false); test(true); } } private static void test(boolean parallel) { Set<List<Integer>> vectors = Collections.newSetFromMap( Collections.synchronizedMap( // should not rely on a mutating hashCode() new IdentityHashMap<>() ) ); IntStream range = IntStream.range(1, 100_000_000); if (parallel) range = range.parallel(); long time = System.nanoTime(); try { ThreadLocal<List<Integer>> lists = ThreadLocal.withInitial(() -> { List<Integer> result = new Vector<>(); vectors.add(result); // avoid GC during run for (int i = 0; i < 1024; i++) result.add(i); return result; }); range.map(i -> lists.get().get(i & 1023)).sum(); } finally { time = System.nanoTime() - time; System.out.printf("%s %dms%n", parallel ? "parallel" : "sequential", (time / 1_000_000)); } } }The results are now all quite similar:
openjdk version "1.8.0_252" OpenJDK Runtime Environment (Zulu 8.46.0.19-CA-macosx) (build 1.8.0_252-b14) OpenJDK 64-Bit Server VM (Zulu 8.46.0.19-CA-macosx) (build 25.252-b14, mixed mode) sequential 1624ms parallel 220ms sequential 1607ms parallel 214ms sequential 1641ms parallel 218ms sequential 1629ms parallel 222ms sequential 1591ms parallel 202ms sequential 1612ms parallel 219ms sequential 1590ms parallel 203ms sequential 1783ms parallel 233ms sequential 1880ms parallel 239ms sequential 1791ms parallel 290ms openjdk version "15-ea" 2020-09-15 OpenJDK Runtime Environment (build 15-ea+23-1098) OpenJDK 64-Bit Server VM (build 15-ea+23-1098, mixed mode, sharing) sequential 2125ms parallel 323ms sequential 1710ms parallel 262ms sequential 2111ms parallel 233ms sequential 1578ms parallel 252ms sequential 1591ms parallel 249ms sequential 1606ms parallel 235ms sequential 1567ms parallel 224ms sequential 1633ms parallel 224ms sequential 1579ms parallel 229ms sequential 1584ms parallel 225ms openjdk version "15-ea" 2020-09-15 OpenJDK Runtime Environment (build 15-ea+28-1400) OpenJDK 64-Bit Server VM (build 15-ea+28-1400, mixed mode, sharing) sequential 1572ms parallel 244ms sequential 1522ms parallel 222ms sequential 1538ms parallel 218ms sequential 1532ms parallel 216ms sequential 1499ms parallel 222ms sequential 1501ms parallel 227ms sequential 1520ms parallel 217ms sequential 1471ms parallel 215ms sequential 1545ms parallel 233ms sequential 1503ms parallel 220msWhat is going on? Please let me know your guesses by return email. Best explanation gets a mention in the next newsletter :-)
Kind regards from Crete
Heinz
Java Specialists Superpack 2020 Our entire Java Specialists Training in One Huge BundleLoading...
Loading...