Java 8 pseudo-sharing and cache row filling -- @ Contended annotation, Java 8 -- @ contended
In my previous article <pseudo-sharing and cache row filling, from Java 6, Java 7 to Java 8>, we demonstrated that in Java 8, you can use @ Contended annotation at the class level to fill in cache rows. In this way, the problem of pseudo-sharing conflicts in the case of multiple threads. If you are interested, you can view this article.
In fact, the @ Contended annotation can also be applied to the Field-Level. When it is applied to the Field Level, the annotated Field will be isolated from other fields, it is loaded on an independent cache row. At the field Level, @ Contended also supports a "contention group" attribute (Class-Level is not supported). The fields of the same group will be continuous in memory, but it is isolated from other fields.
The above is just a general introduction. There is very little information about @ Contended's application to Field-Level, especially the contention group, some comments in the source code, and the JEP-142 (I .e. the proposal to add @ Contended) the description (http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html) in the discussion board, where the description is very detailed (since the discussion occurred at the initial stage of the implementation of @ Contended, it cannot be guaranteed to be exactly the same as the current implementation ), my excerpt and translation are as follows:
@ Contended:
A. Apply Contended on the class:
@Contended public static class ContendedTest2 { private Object plainField1; private Object plainField2; private Object plainField3; private Object plainField4; }
The two ends of the entire field block will be filled in: (The following is the output using-XX: + PrintFieldLayout) (Note: The previous @ 140 represents the address offset of the field in the class)
TestContended$ContendedTest2: field layout Entire class is marked contended @140 --- instance fields start --- @140 "plainField1" Ljava.lang.Object; @144 "plainField2" Ljava.lang.Object; @148 "plainField3" Ljava.lang.Object; @152 "plainField4" Ljava.lang.Object; @288 --- instance fields end --- @288 --- instance ends ---
Note that we use bytes padding -- twice the size of most hardware cache rows -- to avoid pseudo-sharing conflicts caused by prefetch of adjacent sectors.
B. Apply Contended on the field:
public static class ContendedTest1 { @Contended private Object contendedField1; private Object plainField1; private Object plainField2; private Object plainField3; private Object plainField4; }
This field is separated from the continuous field blocks and can be efficiently filled:
TestContended$ContendedTest1: field layout @ 12 --- instance fields start --- @ 12 "plainField1" Ljava.lang.Object; @ 16 "plainField2" Ljava.lang.Object; @ 20 "plainField3" Ljava.lang.Object; @ 24 "plainField4" Ljava.lang.Object; @156 "contendedField1" Ljava.lang.Object; (contended, group = 0) @288 --- instance fields end --- @288 --- instance ends ---
C. annotate multiple fields so that they are filled separately:
public static class ContendedTest4 { @Contended private Object contendedField1; @Contended private Object contendedField2; private Object plainField3; private Object plainField4; }
The two fields to be annotated are independently filled:
TestContended$ContendedTest4: field layout @ 12 --- instance fields start --- @ 12 "plainField3" Ljava.lang.Object; @ 16 "plainField4" Ljava.lang.Object; @148 "contendedField1" Ljava.lang.Object; (contended, group = 0) @280 "contendedField2" Ljava.lang.Object; (contended, group = 0) @416 --- instance fields end --- @416 --- instance ends ---
In some cases, you want to group fields. The fields in the same group conflict with other fields, but they do not exist in the same group. For example, it is common to update two fields simultaneously in the code of the same thread. It is sufficient to add the @ Contended annotation to both fields at the same time (Note: It is too good), but we can remove the padding between them, to optimize their memory space usage. For grouping, we have a parameter "contention group" to describe:
Therefore:
public static class ContendedTest5 { @Contended("updater1") private Object contendedField1; @Contended("updater1") private Object contendedField2; @Contended("updater2") private Object contendedField3; private Object plainField5; private Object plainField6; }
The memory layout is:
TestContended$ContendedTest5: field layout @ 12 --- instance fields start --- @ 12 "plainField5" Ljava.lang.Object; @ 16 "plainField6" Ljava.lang.Object; @148 "contendedField1" Ljava.lang.Object; (contended, group = 12) @152 "contendedField2" Ljava.lang.Object; (contended, group = 12) @284 "contendedField3" Ljava.lang.Object; (contended, group = 15) @416 --- instance fields end --- @416 --- instance ends ---
Note that $ contendedField1 and $ contendedField2 are filled with other fields, but they are close to each other.
The above is a translation of the original implementations of the contact list.
Next, let's perform a test to see if @ Contended can solve the pseudo cache problem at the field level and with grouping.
import sun.misc.Contended;public class VolatileLong { @Contended("group0") public volatile long value1 = 0L; @Contended("group0") public volatile long value2 = 0L; @Contended("group1") public volatile long value3 = 0L; @Contended("group1") public volatile long value4 = 0L; }
We use two threads to modify the field --
Test 1: Thread 0 modifies value1 and value2; thread 1 modifies value3 and value4; they are all in the same group.
Test 2: Thread 0 modifies value1 and value3; thread 1 modifies value2 and value4; they are in different groups.
Test 1:
public final class FalseSharing implements Runnable { public final static long ITERATIONS = 500L * 1000L * 1000L; private static VolatileLong volatileLong; private String groupId; public FalseSharing(String groupId) { this.groupId = groupId; } public static void main(final String[] args) throws Exception { // Thread.sleep(10000); System.out.println("starting...."); volatileLong = new VolatileLong(); final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread t0 = new Thread(new FalseSharing("t0")); Thread t1 = new Thread(new FalseSharing("t1")); t0.start(); t1.start(); t0.join(); t1.join(); } public void run() { long i = ITERATIONS + 1; if (groupId.equals("t0")) { while (0 != --i) { volatileLong.value1 = i; volatileLong.value2 = i; } } else if (groupId.equals("t1")) { while (0 != --i) { volatileLong.value3 = i; volatileLong.value4 = i; } } }}
Test 2: (modify the following part based on the above Code)
public void run() { long i = ITERATIONS + 1; if (groupId.equals("t0")) { while (0 != --i) { volatileLong.value1 = i; volatileLong.value3 = i; } } else if (groupId.equals("t1")) { while (0 != --i) { volatileLong.value2 = i; volatileLong.value4 = i; } } }
Test 1:
starting....duration = 16821484056
Test 2:
starting....duration = 39191867777
It can be seen that if the same thread modifies a field in the same "contention group" and there is no pseudo-sharing conflict, it is more than twice as fast as the pseudo-sharing conflict.
Postscript:
Test 3: Do not use @ Contended
public class VolatileLong { public volatile long value1 = 0L; public volatile long value2 = 0L; public volatile long value3 = 0L; public volatile long value4 = 0L; }
Result:
starting....duration = 38096777198
Refer:
Http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/sun/misc/Contended.java
Http://openjdk.java.net/jeps/142
Http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html