Skip to content

Commit c7e60bb

Browse files
committed
Add requirements document for Java Thread Affinity library and update README with OS support details
1 parent 28c10cb commit c7e60bb

3 files changed

Lines changed: 425 additions & 7 deletions

File tree

README.adoc

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ OpenHFT Java Thread Affinity library
1717
See https://github.com/OpenHFT/Java-Thread-Affinity/tree/master/affinity/src/test/java[affinity/src/test/java]
1818
for working examples of how to use this library.
1919

20+
=== Supported operating systems
21+
22+
The library detects the running platform in `Affinity.java` and selects an
23+
implementation for that OS. Features differ between systems:
24+
25+
* *Linux* - full affinity control via JNA. The implementation can get and set
26+
thread affinity, query the current CPU, and obtain process and thread IDs.
27+
* *Windows* - thread affinity is managed through the kernel API. Process and
28+
thread IDs are available, while `getCpu()` returns `-1`.
29+
* *macOS* - provides process and thread IDs but does not modify affinity and
30+
reports the CPU id as `-1`.
31+
* *Solaris* - mirrors the macOS implementation: only process and thread IDs are
32+
returned with no affinity or CPU querying support.
33+
2034
=== Changes
2135

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

4862
sudo yum install jna
4963

64+
=== Installing JNA on Windows
65+
66+
choco install jna
67+
68+
Or download jna.jar and jna-platform.jar from the JNA project and add them to your classpath.
69+
5070
=== How does CPU allocation work?
5171
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.
5272

5373
The library looks for isolated CPUs determined by looking at the CPUs you are not running on by default.
5474
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.
5575

5676
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.
57-
To control which CPUs a process can use, add -Daffinity.reserved={cpu-mask-in-hex} to the command line of the process.
77+
78+
To control which CPUs a process can use, add `-Daffinity.reserved={cpu-mask-in-hex}`
79+
to the command line of the process. The mask is a hexadecimal bit mask without
80+
the `0x` prefix where bit `0` represents CPU `0`, bit `1` represents CPU `1` and
81+
so on. Multiple CPUs can be specified by setting more than one bit.
82+
83+
For example:
84+
85+
* `-Daffinity.reserved=2` reserves only CPU `1`.
86+
* `-Daffinity.reserved=6` reserves CPUs `1` and `2`.
87+
* `-Daffinity.reserved=10` reserves CPUs `1` and `3` (hexadecimal `a`).
88+
89+
Use an appropriate mask when starting each process to avoid reserving the same
90+
cores for multiple JVMs.
5891

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

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

77110
isolcpus=1,3
78111

112+
Using GRUB
113+
[source]
114+
----
115+
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=1,3 /' /etc/default/grub
116+
sudo update-grub
117+
sudo reboot
118+
----
119+
120+
Using systemd-boot
121+
[source]
122+
----
123+
sudo sed -i 's/^options \(.*\)/options \1 isolcpus=1,3/' /boot/loader/entries/*.conf
124+
sudo reboot
125+
----
126+
79127
== Using AffinityLock
80128

81129
=== Acquiring a CPU lock for a thread
@@ -127,7 +175,21 @@ try (final AffinityLock al = AffinityLock.acquireLock()) {
127175
t.start();
128176
}
129177
----
130-
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.
178+
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.
179+
180+
=== Affinity strategies
181+
The `AffinityStrategies` enum defines hints for selecting a CPU relative to an existing lock.
182+
183+
[options="header",cols="1,3"]
184+
|===
185+
| Strategy | Meaning
186+
187+
|`ANY`|Use any available CPU.
188+
|`SAME_CORE`|Select a CPU on the same core.
189+
|`SAME_SOCKET`|Select a CPU on the same socket but a different core.
190+
|`DIFFERENT_CORE`|Select a CPU on another core (possibly another socket).
191+
|`DIFFERENT_SOCKET`|Select a CPU on a different socket.
192+
|===
131193

132194
=== Getting the thread id
133195
You can get the current thread id using
@@ -157,10 +219,50 @@ long reservedAffinity = AffinityLock.RESERVED_AFFINITY;
157219
----
158220
If you want to get/set the affinity directly you can do
159221
[source, java]
160-
----
222+
----
161223
long currentAffinity = AffinitySupport.getAffinity();
162224
AffinitySupport.setAffinity(1L << 5); // lock to CPU 5.
163-
----
225+
----
226+
227+
=== Understanding dumpLocks() output
228+
229+
Several examples print the current CPU assignments using `AffinityLock.dumpLocks()`.
230+
Each line of the output begins with the zero based CPU id followed by the status
231+
of that CPU. Example output might look like:
232+
233+
[source]
234+
----
235+
0: Reserved for this application
236+
1: Thread[reader,5,main] alive=true
237+
2: General use CPU
238+
3: CPU not available
239+
----
240+
241+
The number on each line is the logical CPU index as recognised by the library.
242+
The text after the colon describes whether that CPU is free, reserved or already
243+
bound to a thread. Use these indices when calling `AffinityLock.acquireLock(n)`
244+
or when constructing explicit affinity masks.
245+
246+
=== Lock file directory
247+
248+
AffinityLock stores a small lock file for each CPU. These files are placed in
249+
the directory specified by the `java.io.tmpdir` system property, which by
250+
default points to your system's temporary directory (usually `/tmp` on Linux).
251+
252+
If you want to keep the lock files elsewhere, set this property before using any
253+
affinity APIs:
254+
255+
[source, bash]
256+
----
257+
java -Djava.io.tmpdir=/path/to/dir ...
258+
----
259+
260+
or in code
261+
262+
[source, java]
263+
----
264+
System.setProperty("java.io.tmpdir", "/path/to/dir");
265+
----
164266

165267
=== Debugging affinity state
166268

@@ -185,6 +287,25 @@ $ for i in "$(ls cpu-*)";
185287
186288
----
187289

290+
== Using AffinityThreadFactory
291+
292+
`AffinityThreadFactory` binds each thread it creates according to a set of
293+
`AffinityStrategy` rules. This allows executors to automatically run tasks on
294+
cores selected by the library.
295+
296+
[source, java]
297+
----
298+
ExecutorService es = Executors.newFixedThreadPool(4,
299+
new AffinityThreadFactory("worker",
300+
AffinityStrategies.SAME_CORE,
301+
AffinityStrategies.DIFFERENT_SOCKET,
302+
AffinityStrategies.ANY));
303+
es.submit(() -> {
304+
// your task here
305+
});
306+
System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks());
307+
----
308+
188309
== Support Material
189310

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

0 commit comments

Comments
 (0)