performance - stati - scheduler



Impossibile evitare i context-switch su un processo avviato da solo su una CPU (2)

Sto studiando come eseguire un processo su una CPU dedicata per evitare i commutatori di contesto. Sulla mia Ubuntu, ho isolato due CPU usando i parametri del kernel "isolcpus = 3,7" e "irqaffinity = 0-2,4-6". Sono sicuro che sia stato correttamente preso in considerazione:

$ cat /proc/cmdline 
BOOT_IMAGE=/boot/vmlinuz-4.8.0-27-generic root=UUID=58c66f12-0588-442b-9bb8-1d2dd833efe2 ro quiet splash isolcpus=3,7 irqaffinity=0-2,4-6 vt.handoff=7

Dopo un riavvio, posso verificare che tutto funzioni come previsto. Su una prima console corro

$ stress -c 24
stress: info: [31717] dispatching hogs: 24 cpu, 0 io, 0 vm, 0 hdd

E su un secondo, usando "top" posso controllare l'utilizzo delle mie CPU:

top - 18:39:07 up 2 days, 20:48, 18 users,  load average: 23,15, 10,46, 4,53
Tasks: 457 total,  26 running, 431 sleeping,   0 stopped,   0 zombie
%Cpu0  :100,0 us,  0,0 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu1  : 98,7 us,  1,3 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu2  : 99,3 us,  0,7 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu3  :  0,0 us,  0,0 sy,  0,0 ni,100,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu4  : 95,7 us,  4,3 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu5  : 98,0 us,  2,0 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu6  : 98,7 us,  1,3 sy,  0,0 ni,  0,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
%Cpu7  :  0,0 us,  0,0 sy,  0,0 ni,100,0 id,  0,0 wa,  0,0 hi,  0,0 si,  0,0 st
KiB Mem :  7855176 total,   385736 free,  5891280 used,  1578160 buff/cache
KiB Swap: 15624188 total, 10414520 free,  5209668 used.   626872 avail Mem 

Le CPU 3 e 7 sono gratuite mentre le altre 6 sono completamente occupate. Belle.

Per il resto del mio test, userò una piccola applicazione che esegue l'elaborazione quasi pura

  1. Utilizza due buffer int della stessa dimensione
  2. Legge uno per uno tutti i valori del primo buffer
    • ogni valore è un indice casuale nel secondo buffer
  3. Legge il valore all'indice nel secondo buffer
  4. Riordina tutti i valori presi dal secondo buffer
  5. Fa tutti i passaggi precedenti per sempre più grande
  6. Alla fine, stampo il numero di switch di contesto CPU volontari e involontari

Ora sto studiando la mia applicazione quando la avvio:

  1. su una CPU non isolata
  2. su una CPU isolata

Lo faccio tramite le seguenti righe di comando:

$ ./TestCpuset              ### launch on any non-isolated CPU
$ taskset -c 7 ./TestCpuset ### launch on isolated CPU 7

Quando vengono lanciati su qualsiasi CPU, i numeri degli switch di contesto cambiano da 20 a ... migliaia

Quando viene lanciato su una CPU isolata, il numero di switch di contesto è quasi costante (tra 10 e 20), anche se avvio in parallelo uno "stress -c 24". (sembra abbastanza normale)

Ma la mia domanda è: perché non è 0 assolutamente 0? Quando un passaggio viene eseguito su un processo, è per sostituirlo con un altro processo? Ma nel mio caso non c'è altro processo da sostituire!

Ho un'ipotesi che è che l'opzione "isolcpus" isolerebbe la CPU da qualsiasi processo (a meno che il processo non fornisse un'affinità della CPU, come quello che viene fatto con "taskset") ma non dalle attività del kernel. Tuttavia, non ho trovato alcuna documentazione al riguardo

Gradirei qualsiasi aiuto per raggiungere 0 context-switch

Cordiali saluti, questa domanda è chiusa a un altro che ho aperto in precedenza: non può allocare esclusivamente una CPU per il mio processo

Ecco il codice del programma che sto usando:

#include <limits.h>
#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

const unsigned int BUFFER_SIZE = 4096;

using namespace std;


class TimedSumComputer
{

public:
  TimedSumComputer() :
    sum(0),
    bufferSize(0),
    valueBuffer(0),
    indexBuffer(0)
  {}


public:
  virtual ~TimedSumComputer()
  {
    resetBuffers();
  }


public:
  void init(unsigned int bufferSize)
  {
    this->bufferSize = bufferSize;
    resetBuffers();
    initValueBuffer();
    initIndexBuffer();
  }


private:
  void resetBuffers() 
  {
    delete [] valueBuffer;
    delete [] indexBuffer;
    valueBuffer = 0;
    indexBuffer = 0;
  }


  void initValueBuffer()
  {
    valueBuffer = new unsigned int[bufferSize];
    for (unsigned int i = 0 ; i < bufferSize ; i++)
    {
      valueBuffer[i] = randomUint();
    }
  }


  static unsigned int randomUint()
  {
    int value = rand() % UINT_MAX;
    return value;
  }


protected:
  void initIndexBuffer()
  {
    indexBuffer = new unsigned int[bufferSize];
    for (unsigned int i = 0 ; i < bufferSize ; i++)
    {
      indexBuffer[i] = rand() % bufferSize;
    }
  }


public:
  unsigned int getSum() const
  {
    return sum;
  }


  unsigned int computeTimeInMicroSeconds()
  {
    struct timeval startTime, endTime;

    gettimeofday(&startTime, NULL);
    unsigned int sum = computeSum();
    gettimeofday(&endTime, NULL);

    return ((endTime.tv_sec - startTime.tv_sec) * 1000 * 1000) + (endTime.tv_usec - startTime.tv_usec);
  }


  unsigned int computeSum()
  {
    sum = 0;

    for (unsigned int i = 0 ; i < bufferSize ; i++)
    {
      unsigned int index = indexBuffer[i];
      sum += valueBuffer[index];
    }

    return sum;
  }


protected:
  unsigned int sum;
  unsigned int bufferSize;
  unsigned int * valueBuffer;
  unsigned int * indexBuffer;

};



unsigned int runTestForBufferSize(TimedSumComputer & timedComputer, unsigned int bufferSize)
{
  timedComputer.init(bufferSize);

  unsigned int timeInMicroSec = timedComputer.computeTimeInMicroSeconds();
  cout << "bufferSize = " << bufferSize << " - time (in micro-sec) = " << timeInMicroSec << endl;
  return timedComputer.getSum();
}



void runTest(TimedSumComputer & timedComputer)
{
  unsigned int result = 0;

  for (unsigned int i = 1 ; i < 10 ; i++)
  {
    result += runTestForBufferSize(timedComputer, BUFFER_SIZE * i);
  }

  unsigned int factor = 1;
  for (unsigned int i = 2 ; i <= 6 ; i++)
  {
    factor *= 10;
    result += runTestForBufferSize(timedComputer, BUFFER_SIZE * factor);
  }

  cout << "result = " << result << endl;
}



void printPid()
{
  cout << "###############################" << endl;
  cout << "Pid = " << getpid() << endl;
  cout << "###############################" << endl;
}



void printNbContextSwitch()
{
  struct rusage usage;
  getrusage(RUSAGE_THREAD, &usage);
  cout << "Number of voluntary context switch:   " << usage.ru_nvcsw << endl;
  cout << "Number of involuntary context switch: " << usage.ru_nivcsw << endl;
}



int main()
{
  printPid();

  TimedSumComputer timedComputer;
  runTest(timedComputer);

  printNbContextSwitch();

  return 0;
}

https://src-bin.com


Answer #1

Oggi ho ottenuto più indizi riguardo al mio problema, mi sono reso conto che dovevo investigare in profondità cosa stava succedendo nello schedulatore del kernel. Ho trovato queste due pagine:

Ho abilitato la traccia dello scheduler mentre la mia applicazione era in esecuzione in quel modo:

# sudo bash
# cd /sys/kernel/debug/tracing
# echo 1 > options/function-trace ; echo function_graph > current_tracer ; echo 1 > tracing_on ; echo 0 > tracing_max_latency ; taskset -c 7 [path-to-my-program]/TestCpuset ; echo 0 > tracing_on
# cat trace

Poiché il mio programma è stato avviato sulla CPU 7 (taskset -c 7), devo filtrare l'output di "traccia"

# grep " 7)" trace

Posso quindi cercare le transizioni, da un processo a un altro:

# grep " 7)" trace | grep "=>"
 ...
 7)  TestCpu-4753  =>  kworker-5866 
 7)  kworker-5866  =>  TestCpu-4753 
 7)  TestCpu-4753  =>   watchdo-26  
 7)   watchdo-26   =>  TestCpu-4753 
 7)  TestCpu-4753  =>  kworker-5866 
 7)  kworker-5866  =>  TestCpu-4753 
 7)  TestCpu-4753  =>  kworker-5866 
 7)  kworker-5866  =>  TestCpu-4753 
 7)  TestCpu-4753  =>  kworker-5866 
 7)  kworker-5866  =>  TestCpu-4753 
 ...

Bingo! Sembra che le opzioni di contesto che sto monitorando siano transizioni a:

  • kworker
  • cane da guardia

Ora devo trovare:

  • quali sono esattamente questi processi / thread? (sembra che siano gestiti dal kernel)
  • Posso evitare che funzionino con le mie CPU dedicate?

Per corso, ancora una volta apprezzerei qualsiasi aiuto :-P


Answer #2

Per il piacere di coloro che trovano questo tramite google (come me), /sys/devices/virtual/workqueue/cpumask controlla dove il kernel può fare la coda dei lavori in coda con WORK_CPU_UNBOUND (Non importa quale cpu). Al momento di scrivere questa risposta, non è impostata sulla stessa maschera di quella che un isolcpus manipola di default.

Una volta che l'ho modificato per non includere la mia cpu isolata, ho visto una quantità significativamente minore (ma non zero) di opzioni di contesto nei miei thread critici. Suppongo che i lavori eseguiti sulla mia cpu isolata debbano averlo richiesto in modo specifico, ad esempio usando schedule_on_each_cpu .





context-switch