digitalmars.D.bugs - [Issue 9763] New: contended and contended("groupName")
- d-bugmail puremagic.com (250/367) Mar 19 2013 http://d.puremagic.com/issues/show_bug.cgi?id=9763
- d-bugmail puremagic.com (26/26) Mar 20 2013 http://d.puremagic.com/issues/show_bug.cgi?id=9763
http://d.puremagic.com/issues/show_bug.cgi?id=9763
Summary: contended and contended("groupName")
Product: D
Version: D2
Platform: All
OS/Version: All
Status: NEW
Severity: enhancement
Priority: P2
Component: DMD
AssignedTo: nobody puremagic.com
ReportedBy: bearophile_hugs eml.cc
See for more info:
http://openjdk.java.net/jeps/142
https://blogs.oracle.com/dave/entry/java_contented_annotation_to_help
The idea is to introduce some way in D to specify that some fields in an
struct/object are probably highly contended across processor cores, so the
compiler can pad (or arrange, in objects) to not share cache lines with other
fields, or other structs, that are likely to be independently accessed.
- - - - - - - - - - - - - - - - - - - -
This is from:
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html
A. Marking the class as contended:
Contended
public static class ContendedTest2 {
private Object plainField1;
private Object plainField2;
private Object plainField3;
private Object plainField4;
}
...makes the entire field block to be padded from the both sides:
(below is the output of new tracing -XX:+PrintFieldLayout)
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 128 bytes, twice the cache line size on most hardware
to adjust for adjacent sector prefetchers extending the false sharing
collisions to two cache lines.
B. Marking the field as contended:
public static class ContendedTest1 {
Contended
private Object contendedField1;
private Object plainField1;
private Object plainField2;
private Object plainField3;
private Object plainField4;
}
...pushes the field out of dense block and effectively applies padding:
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. Marking multiple fields makes each field padded:
public static class ContendedTest4 {
Contended
private Object contendedField1;
Contended
private Object contendedField2;
private Object plainField3;
private Object plainField4;
}
...pushes both fields with individual padding for each:
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 ---
*** IV. Contention groups
There are cases where you want to separate the *group* of fields that
are experiencing contention with everything else but not pairwise. This
is the usual thing for some of the code updating two fields at once.
While marking both with Contended would be sufficient, we can optimize
the memory footprint by not applying padding between them. In order to
demarcate these groups, we have the parameter in the annotation
describing the equivalence class for contention group.
So that:
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;
}
...is laid out as:
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 $contendedField1 and $contendedField2 are padded from everything
else, but still densely packed with each other.
- - - - - - - - - - - - - - - - - - - -
A design for D is similar. For structs the problem is solved with padding
between fields or between structs, while in class instances it's solved with
padding and/or field reordering. The syntax is similar:
struct Test1 {
contended int field1;
int field2;
contended int field3;
}
contended final class Test2 {
contended int field1;
int field2;
contended int field3;
}
contended struct Test3 {
int field1;
int field2;
}
contended final class Test4 {
private Object field1;
private Object field2;
}
- - - - - - - - - - - - - - - - - - - -
Contention groups in D:
struct Test5 {
contended("group1") int field1;
contended("group1") int field2;
contended("group2") int field3;
private int field4;
private int field5;
}
- - - - - - - - - - - - - - - - - - - -
I think the D compiler should ignore contended for const/immutable fields:
alias T = const(int);
struct Test6 {
contended T field1;
int field2;
}
static assert(Test6.sizeof == 8);
- - - - - - - - - - - - - - - - - - - -
An alternative syntax is to use align:
align(cache_line)
align(cache_line, "group1")
Like this:
struct Test7 {
align(cache_line) int field1;
int field2;
align(cache_line) int field3;
}
align(cache_line) class Test8 {
private Object field1;
private Object field2;
}
struct Test9 {
align(cache_line, "group1") int field1;
align(cache_line, "group1") int field2;
align(cache_line, "group2") int field3;
private int field4;
private int field5;
}
But contended is more explicit in its purpose.
- - - - - - - - - - - - - - - - - - - -
Cache lines contention is a well known phenomenon. This is a small example. The
code is adapted from:
http://rosettacode.org/wiki/Atomic_updates#D
// start atomic_updates.d code.
import std.stdio: writeln;
import std.conv: text;
import std.random: uniform, Xorshift;
import std.algorithm: min, max;
import std.parallelism: task;
import core.thread: Thread;
import core.sync.mutex: Mutex;
import core.time: dur;
__gshared uint transfersCount;
final class Buckets(size_t nBuckets) if (nBuckets > 0) {
alias TBucketValue = uint;
private static struct Bucket {
TBucketValue value;
Mutex mtx;
//uint[14] _voidPadding = void;
alias value this;
}
private Bucket[nBuckets] buckets;
private bool running;
public this() {
this.running = true;
foreach (ref b; buckets)
b = Bucket(uniform(0, 100), new Mutex());
}
public TBucketValue opIndex(in size_t index) const pure nothrow {
return buckets[index];
}
public void transfer(in size_t from, in size_t to,
in TBucketValue amount) {
immutable low = min(from, to);
immutable high = max(from, to);
buckets[low].mtx.lock();
buckets[high].mtx.lock();
scope(exit) {
buckets[low].mtx.unlock();
buckets[high].mtx.unlock();
}
immutable realAmount = min(buckets[from].value, amount);
buckets[from] -= realAmount;
buckets[to ] += realAmount;
transfersCount++;
}
property size_t length() const pure nothrow {
return this.buckets.length;
}
void toString(in void delegate(const(char)[]) sink) {
TBucketValue total = 0;
foreach (ref b; buckets) {
b.mtx.lock();
total += b;
}
scope(exit)
foreach (ref b; buckets)
b.mtx.unlock();
sink(text(buckets));
sink(" ");
sink(text(total));
}
}
void randomize(size_t N)(Buckets!N data) {
immutable maxi = data.length - 1;
auto rng = Xorshift(1);
while (data.running) {
immutable i = uniform(0, maxi, rng);
immutable j = uniform(0, maxi, rng);
immutable amount = uniform(0, 20, rng);
data.transfer(i, j, amount);
}
}
void equalize(size_t N)(Buckets!N data) {
immutable maxi = data.length - 1;
auto rng = Xorshift(1);
while (data.running) {
immutable i = uniform(0, maxi, rng);
immutable j = uniform(0, maxi, rng);
immutable a = data[i];
immutable b = data[j];
if (a > b)
data.transfer(i, j, (a - b) / 2);
else
data.transfer(j, i, (b - a) / 2);
}
}
void display(size_t N)(Buckets!N data) {
foreach (immutable _; 0 .. 10) {
writeln(transfersCount, " ", data);
transfersCount = 0;
Thread.sleep(dur!"msecs"(1000));
}
data.running = false;
}
void main() {
writeln("N. transfers, buckets, buckets sum:");
auto data = new Buckets!20();
task!randomize(data).executeInNewThread();
task!equalize(data).executeInNewThread();
task!display(data).executeInNewThread();
}
// end atomic_updates.d code.
Timings by nazriel on IRC, done with dmd on an Intel i5 2,4ghz, arch linux
x86_64 with:
-release -inline -O -noboundscheck
Without padding in struct Bucket:
N. transfers, buckets, buckets sum:
85 [0, 5, 0, 51, 5, 25, 89, 0, 147, 93, 47, 2, 76, 43, 76, 69, 0, 62, 67, 51]
908
6246828 [35, 51, 38, 25, 48, 88, 11, 23, 39, 54, 92, 13, 69, 48, 29, 48, 52,
26, 68, 51] 908
6322759 [60, 40, 43, 55, 44, 39, 49, 61, 38, 44, 51, 42, 18, 37, 34, 44, 50,
48, 60, 51] 908
6103135 [29, 63, 74, 44, 44, 48, 44, 53, 35, 62, 35, 44, 44, 44, 34, 47, 31,
40, 42, 51] 908
6567254 [36, 39, 42, 49, 70, 36, 10, 28, 51, 39, 55, 42, 35, 72, 42, 65, 35,
42, 69, 51] 908
6274262 [55, 63, 27, 40, 31, 40, 41, 32, 20, 44, 45, 95, 42, 54, 45, 38, 76,
40, 29, 51] 908
6171490 [39, 24, 61, 56, 42, 51, 39, 56, 62, 43, 72, 39, 56, 13, 32, 67, 45,
48, 12, 51] 908
6211137 [29, 39, 36, 70, 16, 41, 48, 35, 74, 20, 65, 59, 48, 48, 43, 59, 54,
30, 43, 51] 908
6202907 [51, 16, 80, 36, 25, 42, 43, 58, 48, 41, 41, 35, 80, 49, 54, 24, 40,
44, 50, 51] 908
6350466 [50, 35, 37, 66, 46, 65, 38, 48, 52, 47, 51, 46, 20, 37, 34, 46, 51,
38, 50, 51] 908
With padding in struct Bucket:
N. transfers, buckets, buckets sum:
112 [22, 41, 41, 54, 54, 58, 52, 32, 56, 53, 49, 64, 59, 65, 56, 74, 54, 59,
66, 55] 1064
6698768 [56, 58, 54, 29, 38, 54, 58, 55, 54, 72, 26, 57, 83, 51, 49, 45, 56,
57, 57, 55] 1064
8371235 [53, 53, 53, 53, 52, 53, 53, 54, 53, 54, 53, 52, 53, 54, 53, 54, 53,
53, 53, 55] 1064
7680510 [73, 71, 16, 53, 20, 55, 84, 57, 55, 53, 49, 55, 57, 50, 52, 68, 53,
35, 53, 55] 1064
6675895 [45, 32, 79, 46, 48, 29, 65, 79, 13, 79, 0, 79, 61, 65, 40, 63, 65, 61,
60, 55] 1064
7865702 [31, 63, 58, 76, 54, 43, 49, 51, 51, 63, 54, 29, 47, 66, 54, 62, 54,
56, 48, 55] 1064
8068515 [51, 69, 49, 54, 54, 59, 51, 58, 44, 46, 58, 56, 48, 57, 59, 48, 38,
56, 54, 55] 1064
6718293 [40, 55, 52, 55, 77, 51, 40, 57, 87, 37, 56, 56, 49, 69, 65, 17, 13,
54, 79, 55] 1064
6512280 [53, 34, 21, 69, 47, 69, 43, 25, 61, 43, 63, 52, 97, 52, 50, 52, 49,
78, 51, 55] 1064
7408234 [51, 44, 54, 56, 31, 55, 34, 59, 72, 69, 56, 50, 58, 70, 45, 67, 48,
41, 49, 55] 1064
The numbers in the first column like 6274262 represent the number of updated in
a second. With the padding there is less cache lines contention between two
cores, so it performs more atomic updated in a second, like 7408234.
That manual padding in Bucket is similar to:
contended private static struct Bucket {
TBucketValue value;
Mutex mtx;
alias value this;
}
One difference is that in the Java example the padding is on both sides, and
it's twice larger, as explained:
Note that we use 128 bytes, twice the cache line size on most hardware
to adjust for adjacent sector prefetchers extending the false sharing
collisions to two cache lines.
- - - - - - - - - - - - - - - - - - - -
--
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
Mar 19 2013
http://d.puremagic.com/issues/show_bug.cgi?id=9763
A current workaround is to use align (with a value, because of Issue 9766 ):
align(128) struct Test3 {
int field1;
int field2;
}
pragma(msg, "Test3:");
pragma(msg, Test3.field1.offsetof);
pragma(msg, Test3.field2.offsetof);
pragma(msg, "Total size:");
pragma(msg, Test3.sizeof);
pragma(msg, "");
The print shows there is trailing padding (no leading padding):
Test3:
0u
4u
Total size:
128u
Adding align(128) on some fields of struct/object allows to introduce
intermediate padding, but it's tricky to get all the padding right. But
contended adapts automatically the padding needed on different CPUs and makes
the creation of spaces and groups simpler.
--
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
Mar 20 2013








d-bugmail puremagic.com