Java Thread Affinity supports groups of threads.

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]


Comments

  1. I got this error during the test phase :

    Running 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

    ReplyDelete
  2. @Egor, thank you for testing that. I have added this code to the test instead.

    if (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;
    }

    ReplyDelete
  3. 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.

    I 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...

    ReplyDelete
  4. 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.

    ReplyDelete
  5. I 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
  6. @BegemoT Currently the interface is

    public 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.

    ReplyDelete

Post a Comment

Popular posts from this blog

Low Latency Microservices, A Retrospective

Unusual Java: StackTrace Extends Throwable

System wide unique nanosecond timestamps