/**
* Copyright 2013, Landz and its contributors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package z.znr;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static z.util.Throwables.uncheckTo;
import static z.znr.MethodHandles.asm;
/**
* {@link Affinity} is used to support bind your java thread to some
* hardware presentations, that is Socket/PhysicalCore/VirtualCore. <p>
* <p>
* Concept explained:
* <p> Socket - physical packages which mounted on your machine
* <p> PhysicalCore - one package/die may have several physical cores
* <p> VirtualCore - if your physical core support hyperthreading technology,
* then you will have more than one virtual cores on a
* physical core.
* <p> There is a 1-1 mapping between the above concepts
* to Linux's /proc/cpuinfo:
* <p> Socket -> "physical id"
* <p> PhysicalCore -> "core id"
* <p> VirtualCore -> "processor"
* <p> But, we think the above concepts are more clear for the massive.<p>
* Then, the concept "Id" is used to index into these hardware presentations.
* <p>
* A DSL in {@link Affinity.Topology} has been provided to work with
* above concepts. <p>
* <p>
* Note:
* <p>1. now it only supports that the thread which setting the affinity itself
* should be the current running thread;
* <p>2. this class intentionally only supports the Linux/x86-64 platform.
* <p>3. all Id indexings are 0-based and kept the below invariants:
* <p> 0<= socketId < number of Sockets
* <p> 0<= physicalCoreId < number of PhysicalCores per Socket
* <p> 0<= virtualCoreId < number of VirtualCores per PhysicalCore
*
* <p>
*
*/
public class Affinity {
private static int nSockets = 0;
private static int nPhysicalCores = 0;
private static int nVirtualCores = 0;
private static int nPhysicalCoresPerSocket = 0;
private static int nVirtualCoresPerPhysicalCore = 0;
private static void readTopology() {
Path cpuinfoPath = Paths.get("/proc/cpuinfo");
if (!Files.exists(cpuinfoPath))
throw new RuntimeException(
"/proc/cpuinfo does not exist, however this invocation is only for Linux.");
//TODO: not closed?
List<String> lines = uncheckTo(() -> Files.readAllLines(cpuinfoPath));
//how bad performance fluent style:)
nVirtualCores =
(int)lines.stream().
filter(s -> s.startsWith("processor")).count();
nPhysicalCores =
(int)lines.stream().
filter(s -> s.startsWith("core id")).distinct().count();
nSockets =
(int)lines.stream().
filter(s -> s.startsWith("physical id")).distinct().count();
nPhysicalCoresPerSocket = nPhysicalCores/nSockets;
nVirtualCoresPerPhysicalCore = nVirtualCores/nPhysicalCores;
}
public static void printTopology() {
ensureTopologyRead();
System.out.printf("Your machine has %d sockets, " +
"%d physical cores[%d physical cores per socket], " +
"%d virtual cores[%d virtual cores per physical core].\n",
nSockets,
nPhysicalCores, nPhysicalCoresPerSocket,
nVirtualCores, nVirtualCoresPerPhysicalCore);
}
private static void ensureTopologyRead() {
if (nVirtualCores==0)
readTopology();
}
public static int getNumberOfVirtualCores() {
ensureTopologyRead();
return nVirtualCores;
}
public static boolean bindTo(TopologyBasedAffinityMask mask) {
return LibC.setAffinity(0, new LibC.Cpuset(mask.getAffinityMask()))
==0 ? true:false;
}
/**
* TODO: we now only support 64 cpus(first ulong), it is easy to expand to
* more cpus. But, it is better to have one true machine to confirm:)<p>
* TODO: provide exclusive and/or setAffinityMask APIs <p>
* TODO: use landz contract API<p>
*
*/
public static class Topology implements
TopologySocket,
TopologySocketPhysicalCore,
TopologyBasedAffinityMask {
private int processorId = 0;
private int socketId = 0;
private int physicalCoreId = 0;
private int virtualCoreId = 0;
private Topology() {};
/**
* Note: make sure you are indexing socket in the right range
* @param id
* @return
*/
public static TopologySocket socket(int id) {
ensureTopologyRead();
if (id<0 || id>=nSockets)
throw new RuntimeException(
"the input socket id is out of measured scope");
if (nVirtualCores>64)
throw new RuntimeException(
"Topology DSL does not support more than 64 CPUs machine now. " +
"Issue this to Landz to help us to improve this for you!");
Topology topology = new Topology();
topology.socketId = id;
return topology;
}
@Override
public TopologySocketPhysicalCore physicalCore(int id) {
if (id<0 || id>=nPhysicalCoresPerSocket)
throw new RuntimeException(
"the input physicalCore id is out of measured scope");
this.physicalCoreId = id;
return this;
}
@Override
public TopologyBasedAffinityMask virtualCore(int id) {
if (id<0 || id>=nVirtualCoresPerPhysicalCore)
throw new RuntimeException(
"the input virtualCore id is out of measured scope");
this.virtualCoreId = id;
return this;
}
/**
* Other than using {@link #socketId} API, you can assign a processor Id
* directly.
* <p>
* Note: make sure you are indexing processor in the right range
* @param id
* @return
*/
public TopologyBasedAffinityMask processor(int id) {
ensureTopologyRead();
if (id<0 || id>=nVirtualCores)
this.processorId = id;
return this;
}
@Override
public long getAffinityMask() {
processorId =
virtualCoreId*nPhysicalCoresPerSocket*nSockets+
physicalCoreId*nSockets+
socketId;
return 1L<<processorId;
}
}
public static interface TopologySocket {
public TopologySocketPhysicalCore physicalCore(int id);
}
public static interface TopologySocketPhysicalCore {
public TopologyBasedAffinityMask virtualCore(int id);
}
public static interface TopologyBasedAffinityMask {
public long getAffinityMask();
}
}