Overview
The
Java Thread Affinity Library version 1.3, supports assigning groups of threads together by Socket, Core, or a custom strategy.
Determining the grouping of threads
The following
Affinity Lock binding example, shows how you can choose to take either different sockets or cores, or share the same sockets or cores.
It creates a thread lock for "main". For the reader and writer threads it tries to get a different socket or core to "main", and reserve a cpu on the same core or socket as each other.
AffinityLock al = AffinityLock.acquireLock();
try {
// find a cpu on a different socket, otherwise a different core.
AffinityLock readerLock = al.acquireLock(DIFFERENT_SOCKET, DIFFERENT_CORE);
new Thread(new SleepRunnable(readerLock), "reader").start();
// find a cpu on the same core, or the same socket, or any free cpu.
AffinityLock writerLock = readerLock.acquireLock(SAME_CORE, SAME_SOCKET, ANY);
new Thread(new SleepRunnable(writerLock), "writer").start();
Thread.sleep(200);
} finally {
al.release();
}
// re-use the same cpu for the engine.
new Thread(new SleepRunnable(al), "engine").start();
When this runs you can see that
reader gets cpu 6 which is on a different core to
main on cpu 7, however
writer is on cpu 2 which is another thread on the same core as
reader.
Jan 12, 2012 2:58:56 PM vanilla.java.affinity.AffinityLock bind
INFO: Assigning cpu 7 to Thread[main,5,main]
Jan 12, 2012 2:58:56 PM vanilla.java.affinity.AffinityLock bind
INFO: Assigning cpu 6 to Thread[reader,5,main]
Jan 12, 2012 2:58:56 PM vanilla.java.affinity.AffinityLock bind
INFO: Assigning cpu 2 to Thread[writer,5,main]
Jan 12, 2012 2:58:56 PM vanilla.java.affinity.AffinityLock release
INFO: Releasing cpu 7 from Thread[main,5,main]
Jan 12, 2012 2:58:56 PM vanilla.java.affinity.AffinityLock bind
INFO: Assigning cpu 7 to Thread[engine,5,main]
The assignment of CPUs is
0: General use CPU
1: General use CPU
2: Thread[writer,5,main] alive=true
3: Reserved for this application
4: General use CPU
5: General use CPU
6: Thread[reader,5,main] alive=true
7: Thread[engine,5,main] alive=true
Jan 12, 2012 2:58:57 PM vanilla.java.affinity.AffinityLock release
INFO: Releasing cpu 6 from Thread[reader,5,main]
Jan 12, 2012 2:58:57 PM vanilla.java.affinity.AffinityLock release
INFO: Releasing cpu 2 from Thread[writer,5,main]
Jan 12, 2012 2:58:57 PM vanilla.java.affinity.AffinityLock release
INFO: Releasing cpu 7 from Thread[engine,5,main]
I got this error during the test phase :
ReplyDeleteRunning vanilla.java.affinity.AffinityLockTest
0: General use CPU
1: CPU not available
2: Thread[logger,5,main] alive=true
3: Thread[engine,5,main] alive=true
4: General use CPU
5: CPU not available
6: Thread[main,5,main] alive=false
7: Thread[tcp,5,main] alive=true
Jan 13, 2012 8:28:19 AM vanilla.java.affinity.AffinityLock acquireLock
WARNING: No reservable CPU for Thread[main,5,main]
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.026 sec <<< FAILURE!
Results :
Failed tests:
assignReleaseThread(vanilla.java.affinity.AffinityLockTest): expected:<1> but was:<4>
Tests run: 11, Failures: 1, Errors: 0, Skipped: 2
@Egor, thank you for testing that. I have added this code to the test instead.
ReplyDeleteif (AffinityLock.RESERVED_AFFINITY == 0) {
System.out.println("Cannot run affinity test as no threads gave been reserved.");
System.out.println("Use isocpus= in grub.conf or use -D" + AffinityLock.AFFINITY_RESERVED + "={hex mask}");
return;
}
It is an excellent improvement! But from my point of view it will be more pleasant to have higher abstraction -- not to specify exactly which core or socket, but to specify the intention. I may want to have this thread "farther" or "closer" (in terms of memory/interconnect cost) to those thread. And it is the deal of lib to find the way to implement my intention, knowing the topology of CPUs.
ReplyDeleteI also have in mind option to specify "metrics" in which I want my thread to be closer/farther -- it can be "memory" or "interconnect" for example...
I have added the ability to reserve a whole core. with AffinityLock.acquireCore(). This way you can enable hyper threading and allow this for threads which make sense but for the most critical threads you may want it to no compete with other threads.
ReplyDeleteI was talking about to improve API this way: AffinityLock.acquireLock( ICoreFitnessFunction f). When you can define "fitness" as "lowest interaction overhead" (lowest interconnect cost, possible share LN cache), or "lowest CPU contention overhead" (do not share HT core), or "lowest memory contention overhead" (do _not_ share cache), or some combination of this. And it is library job to take real CPU topology and find bet fit according to your definition.
ReplyDelete@BegemoT Currently the interface is
ReplyDeletepublic interface AffinityStrategy {
/**
* @param cpuId to cpudId to compare
* @param cpuId2 with a second cpuId
* @return true if it matches the criteria.
*/
public boolean matches(int cpuId, int cpuId2);
}
which can use the CpuLayout to determine if its acceptable.
The line
readerLock.acquireLock(SAME_CORE, SAME_SOCKET, ANY);
uses the predefined strategies of either SAME_CORE otherwise SAME_SOCKET otherwise ANY. This allows you to specify closest, close or any. Similarly
al.acquireLock(DIFFERENT_SOCKET, DIFFERENT_CORE);
Allows you to define the farthest, or far cpu available to be reserved.
You can define your own strategies.