src/Pure/System/numa.scala
author wenzelm
Sun, 14 Mar 2021 18:32:11 +0100
changeset 73434 00b77365552e
parent 71601 97ccf48c2f0c
child 75393 87ebf5a50283
permissions -rw-r--r--
clarified signature: refer to file name instead of file content;

/*  Title:      Pure/System/numa.scala
    Author:     Makarius

Support for Non-Uniform Memory Access of separate CPU nodes.
*/

package isabelle


object NUMA
{
  /* available nodes */

  def nodes(): List[Int] =
  {
    val numa_nodes_linux = Path.explode("/sys/devices/system/node/online")

    val Single = """^(\d+)$""".r
    val Multiple = """^(\d+)-(\d+)$""".r

    def read(s: String): List[Int] =
      s match {
        case Single(Value.Int(i)) => List(i)
        case Multiple(Value.Int(i), Value.Int(j)) => (i to j).toList
        case _ => error("Cannot parse CPU node specification: " + quote(s))
      }

    if (numa_nodes_linux.is_file) {
      space_explode(',', File.read(numa_nodes_linux).trim).flatMap(read)
    }
    else Nil
  }


  /* CPU policy via numactl tool */

  lazy val numactl_available: Boolean = Isabelle_System.bash("numactl -m0 -N0 true").ok

  def policy(node: Int): String =
    if (numactl_available) "numactl -m" + node + " -N" + node else ""

  def policy_options(options: Options, numa_node: Option[Int] = Some(0)): Options =
    numa_node match {
      case None => options
      case Some(n) => options.string("ML_process_policy") = policy(n)
    }


  /* shuffling of CPU nodes */

  def enabled: Boolean =
    try { nodes().length >= 2 && numactl_available }
    catch { case ERROR(_) => false }

  def enabled_warning(progress: Progress, enabled: Boolean): Boolean =
  {
    def warning =
      if (nodes().length < 2) Some("no NUMA nodes available")
      else if (!numactl_available) Some("bad numactl tool")
      else None

    enabled &&
      (warning match {
        case Some(s) => progress.echo_warning("Shuffling of CPU nodes is disabled: " + s); false
        case _ => true
      })
  }

  class Nodes(enabled: Boolean = true)
  {
    private val available = nodes().zipWithIndex
    private var next_index = 0

    def next(used: Int => Boolean = _ => false): Option[Int] = synchronized {
      if (!enabled || available.isEmpty) None
      else {
        val candidates = available.drop(next_index) ::: available.take(next_index)
        val (n, i) =
          candidates.find({ case (n, i) => i == next_index && !used(n) }) orElse
            candidates.find({ case (n, _) => !used(n) }) getOrElse candidates.head
        next_index = (i + 1) % available.length
        Some(n)
      }
    }
  }
}