Skip to content
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

Documentación Técnica: Asignación de Afinidad de Procesos por Núcleos en Globalscan 7 Light Citrus #9

Open
amores96 opened this issue Apr 2, 2024 · 0 comments

Comments

@amores96
Copy link
Owner

amores96 commented Apr 2, 2024

Version: 1.0
Fecha modificación: 02/04/2024

Índice

Contexto

Asignación de Afinidad por Core

Contexto

Esta documentación explica el procedimiento técnico para asignar la afinidad de procesos a núcleos específicos en Globalscan, utilizando el plugin Citrus Light, en un servidor con CPU Intel Xeon Gold 6226.

Versión

La versión Light de este plugin ejecuta cuatro líneas por servidor bi-procesador, resultando en dos líneas del calibrador por CPU.
La CPU Intel Xeon Gold 6226 posee las siguientes características:

  • Cantidad de núcleos: 12
  • Cantidad de subprocesos: 24
  • Frecuencia turbo máxima: 3.7 GHz
  • Frecuencia básica del procesador: 2.7 GHz
  • Caché: 19.25 MB
  • Tipos de memoria: DDR4-2933
  • Máxima velocidad de memoria: 2933 MHz
  • Cantidad máxima de canales de memoria: 6

Configuración Hardware

Se describen los parámetros de la BIOS que deben modificarse para realizar correctamente la asignación de afinidad de procesos en Globalscan. Los ajustes involucran exclusivamente los parámetros relacionados con los nodos NUMA.
*Nodos NUMA → Optimizan la organización de memoria y procesadores en grupos para reducir la latencia y mejorar el rendimiento en sistemas multiprocesador.
Ajustes:

  • Node Interleaving = Disabled → Mantiene la estructura NUMA, asignando memoria específicamente a cada nodo NUMA para un acceso localizado y eficiente.
  • Numa group size optimization = Flat → Destina memoria exclusivamente a nodos NUMA individuales, maximizando la eficiencia de acceso local.
  • Sub-numa clustering = Enabled → Divide cada nodo NUMA en unidades más pequeñas, incrementando el número de nodos NUMA por CPU para una gestión más detallada.

Configuración de GlobalScan en el AdvancedConfig

Se deben mantener los siguientes parámetros configurados por el preset G7L Citrus:

  • Number of consecutive lanes on same CPU: 1 (*Es por nodo NUMA, no por CPU)
  • Max degree of parallelism in TPL: 1
  • Number of threads used for TIF: 4

Según esta configuración, se ejecutará una línea con 4 TIF y 1 TIC en cada nodo NUMA.

Asignación de Afinidad por Core

Estructura de Datos

El archivo ClassTraitementImage.cs administra la asignación de afinidad, interactuando con la API NumaControl para asignar núcleos a procesos específicos basándose en la configuración de nodos NUMA. En NumaControl.cs se encuentran todos los métodos y llamadas a esta API.
Se implementó el siguiente fragmento de código para asignar la afinidad de cada proceso a un núcleo dentro de un nodo NUMA específico.

Código

if (config.fruitProcessingLibVersion == "G7L Citrus" && isIntelArchitecture_6226) {

    if (glob.config.nbConsecutiveLaneOnSameCPU != 1) {
    throw new Exception("The number of consecutive lanes on the same CPU must be 1.");}
    if (glob.memconfig_nombre_ligne > nb_numa_nodes) {
    throw new Exception($"The number of lines cannot exceed the number of NUMA nodes = {nb_numa_nodes}.");}
    if (nb_cores_used_per_lane >= nb_cores_per_numa) {
    throw new Exception($"The maximum number of processes (TIC + TIFs) cannot exceed the number of cores per NUMA node = {nb_cores_per_numa}.");}

    #region Intel 6226 -> NUMA nodes and Cores afinity
    for (int line = 0; line < glob.memconfig_nombre_ligne; line++) {

    NumaControl.GROUP_AFFINITY_64 numaAffinity = new NumaControl.GROUP_AFFINITY_64();
    NumaControl.GetNumaNodeProcessorMaskEx((byte)(line), out numaAffinity);
    List<int> coreNumbers = NumaControl.GetCoreNumbersFromNumaMask(numaAffinity);

    for (int process = 0; process <= PitchParity; process++) {

        int coreNumber = coreNumbers.Count != 0 ? coreNumbers[process % coreNumbers.Count] : 0;
        NumaControl.GROUP_AFFINITY_64 coreAffinity = new NumaControl.GROUP_AFFINITY_64(coreNumber);

        if (process != PitchParity) CpuAffinityTifs[line][process] = coreAffinity;
        else CpuAffinityTics[line] = coreAffinity;

    }

    Log.Debug($"Affinity assigned by NUMA nodes and Cores per process done well using Intel 6226 architecture.");
    }
    #endregion
}

A continuación, se explica cada parte del código:

Lectura de la Máscara de Afinidad por nodo NUMA

Primero, se identifican los núcleos de CPU disponibles en un nodo NUMA específico leyendo su máscara de afinidad:

NumaControl.GROUP_AFFINITY_64 numaAffinity = new NumaControl.GROUP_AFFINITY_64();
NumaControl.GetNumaNodeProcessorMaskEx((byte)(line), out numaAffinity);

La máscara de afinidad representa, mediante un valor binario, la presencia (1) o ausencia (0) de un núcleo en el nodo NUMA.

Transformación de la Máscara en una Lista de Enteros

Posteriormente, esta máscara se convierte en una lista de enteros, donde cada entero indica un núcleo disponible en el nodo NUMA:

List<int> coreNumbers = NumaControl.GetCoreNumbersFromNumaMask(numaAffinity);

La función GetCoreNumbersFromNumaMask transforma la máscara en una lista de enteros, indicando los núcleos disponibles.
Ejemplo con la Máscara 231:
Para una máscara de 231 (equivalente en binario a 0000 0000 0000 0000 1110 0111), los núcleos 1, 2, 3, 6, 7 y 8 están disponibles, representados como [0, 1, 2, 5, 6, 7].

public static List<int> GetCoreNumbersFromNumaMask(GROUP_AFFINITY_64 _numaMask) {
  var coreNumbers = new List<int>();
  int coreNumber = 0;
  ulong mask = _numaMask.Mask;
  while (mask > 0) {
    if ((mask & 1) == 1) {
      coreNumbers.Add(coreNumber);
    }
    coreNumber++;
    mask >>= 1;
  }
  return coreNumbers;
}

Afinidad de Cada Proceso a un Núcleo

Finalmente, se asigna cada proceso a un núcleo de la lista, basándose en el índice del proceso, garantizando así que cada proceso tenga un núcleo asignado dentro de un nodo NUMA específico, y definiendo cada línea del calibrador a un nodo NUMA único.

for (int process = 0; process <= PitchParity; process++) {
  int coreNumber = coreNumbers.Count != 0 ? coreNumbers[process % coreNumbers.Count] : 0;
  NumaControl.GROUP_AFFINITY_64 coreAffinity = new NumaControl.GROUP_AFFINITY_64(coreNumber);

  if (process != PitchParity) CpuAffinityTifs[line][process] = coreAffinity; // After the last TIF we assign the TIC.
  else CpuAffinityTics[line] = coreAffinity;
}

Esta lógica nos permite asignar la afinidad de cada linea a un nodo NUMA, y los procesos de esa linea a núcleos especificos del nodo NUMA en el que se ejecuta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant