Skip to content

Addtional docs #177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 125 additions & 4 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ OpenHFT Java Thread Affinity library
See https://github.com/OpenHFT/Java-Thread-Affinity/tree/master/affinity/src/test/java[affinity/src/test/java]
for working examples of how to use this library.

=== Supported operating systems

The library detects the running platform in `Affinity.java` and selects an
implementation for that OS. Features differ between systems:

* *Linux* - full affinity control via JNA. The implementation can get and set
thread affinity, query the current CPU, and obtain process and thread IDs.
* *Windows* - thread affinity is managed through the kernel API. Process and
thread IDs are available, while `getCpu()` returns `-1`.
* *macOS* - provides process and thread IDs but does not modify affinity and
reports the CPU id as `-1`.
* *Solaris* - mirrors the macOS implementation: only process and thread IDs are
returned with no affinity or CPU querying support.

=== Changes

* V3.2.0 - Add support for text configuration
Expand Down Expand Up @@ -47,14 +61,33 @@ for the artifacts `jna` and `jna-platform` in the project's `pom` file.

sudo yum install jna

=== Installing JNA on Windows

choco install jna

Or download jna.jar and jna-platform.jar from the JNA project and add them to your classpath.

=== How does CPU allocation work?
The library will read your `/proc/cpuinfo` if you have one or provide one and it will determine your CPU layout. If you don't have one it will assume every CPU is on one CPU socket.

The library looks for isolated CPUs determined by looking at the CPUs you are not running on by default.
i.e. if you have 16 CPUs but 8 of them are not available for general use (as determined by the affinity of the process on startup) it will start assigning to those CPUs.

Note: if you have more than one process using this library you need to specify which CPUs the process can use otherwise it will assign the same CPUs to both processes.
To control which CPUs a process can use, add -Daffinity.reserved={cpu-mask-in-hex} to the command line of the process.

To control which CPUs a process can use, add `-Daffinity.reserved={cpu-mask-in-hex}`
to the command line of the process. The mask is a hexadecimal bit mask without
the `0x` prefix where bit `0` represents CPU `0`, bit `1` represents CPU `1` and
so on. Multiple CPUs can be specified by setting more than one bit.

For example:

* `-Daffinity.reserved=2` reserves only CPU `1`.
* `-Daffinity.reserved=6` reserves CPUs `1` and `2`.
* `-Daffinity.reserved=10` reserves CPUs `1` and `3` (hexadecimal `a`).

Use an appropriate mask when starting each process to avoid reserving the same
cores for multiple JVMs.

Note: the CPU 0 is reserved for the Operating System, it has to run somewhere.

Expand All @@ -76,6 +109,21 @@ To isolate the 1st and 3rd CPU cores (CPU numbers start from 0) on your system,

isolcpus=1,3

Using GRUB
[source]
----
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=1,3 /' /etc/default/grub
sudo update-grub
sudo reboot
----

Using systemd-boot
[source]
----
sudo sed -i 's/^options \(.*\)/options \1 isolcpus=1,3/' /boot/loader/entries/*.conf
sudo reboot
----

== Using AffinityLock

=== Acquiring a CPU lock for a thread
Expand Down Expand Up @@ -127,7 +175,21 @@ try (final AffinityLock al = AffinityLock.acquireLock()) {
t.start();
}
----
In this example, the library will prefer a free CPU on the same Socket as the first thread, otherwise it will pick any free CPU.
In this example, the library will prefer a free CPU on the same Socket as the first thread, otherwise it will pick any free CPU.

=== Affinity strategies
The `AffinityStrategies` enum defines hints for selecting a CPU relative to an existing lock.

[options="header",cols="1,3"]
|===
| Strategy | Meaning

|`ANY`|Use any available CPU.
|`SAME_CORE`|Select a CPU on the same core.
|`SAME_SOCKET`|Select a CPU on the same socket but a different core.
|`DIFFERENT_CORE`|Select a CPU on another core (possibly another socket).
|`DIFFERENT_SOCKET`|Select a CPU on a different socket.
|===

=== Getting the thread id
You can get the current thread id using
Expand Down Expand Up @@ -157,10 +219,50 @@ long reservedAffinity = AffinityLock.RESERVED_AFFINITY;
----
If you want to get/set the affinity directly you can do
[source, java]
----
----
long currentAffinity = AffinitySupport.getAffinity();
AffinitySupport.setAffinity(1L << 5); // lock to CPU 5.
----
----

=== Understanding dumpLocks() output

Several examples print the current CPU assignments using `AffinityLock.dumpLocks()`.
Each line of the output begins with the zero based CPU id followed by the status
of that CPU. Example output might look like:

[source]
----
0: Reserved for this application
1: Thread[reader,5,main] alive=true
2: General use CPU
3: CPU not available
----

The number on each line is the logical CPU index as recognised by the library.
The text after the colon describes whether that CPU is free, reserved or already
bound to a thread. Use these indices when calling `AffinityLock.acquireLock(n)`
or when constructing explicit affinity masks.

=== Lock file directory

AffinityLock stores a small lock file for each CPU. These files are placed in
the directory specified by the `java.io.tmpdir` system property, which by
default points to your system's temporary directory (usually `/tmp` on Linux).

If you want to keep the lock files elsewhere, set this property before using any
affinity APIs:

[source, bash]
----
java -Djava.io.tmpdir=/path/to/dir ...
----

or in code

[source, java]
----
System.setProperty("java.io.tmpdir", "/path/to/dir");
----

=== Debugging affinity state

Expand All @@ -185,6 +287,25 @@ $ for i in "$(ls cpu-*)";

----

== Using AffinityThreadFactory

`AffinityThreadFactory` binds each thread it creates according to a set of
`AffinityStrategy` rules. This allows executors to automatically run tasks on
cores selected by the library.

[source, java]
----
ExecutorService es = Executors.newFixedThreadPool(4,
new AffinityThreadFactory("worker",
AffinityStrategies.SAME_CORE,
AffinityStrategies.DIFFERENT_SOCKET,
AffinityStrategies.ANY));
es.submit(() -> {
// your task here
});
System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks());
----

== Support Material

https://groups.google.com/forum/?hl=en-GB#!forum/java-thread-affinity[Java Thread Affinity support group]
Expand Down
Loading