Skip to content

Utilizzo elevato della CPU ma carico medio basso

Questo gruppo specializzato dopo alcuni giorni di ricerca e raccolta di informazioni, abbiamo trovato i dati necessari, il nostro desiderio è che sia utile per il tuo lavoro.

Soluzione:

Soluzione 1:

Almeno su Linux, la media del carico e l'utilizzo della CPU sono in realtà due cose diverse. La media del carico è una misura di quanti task sono in attesa nella coda di esecuzione del kernel (non solo il tempo della CPU ma anche l'attività del disco) in un periodo di tempo. L'utilizzo della CPU è una misura di quanto è occupata la CPU in questo momento. Il carico massimo che un singolo thread della CPU fissato al 100% per un minuto può "contribuire" alla media di carico di 1 minuto è 1. Una CPU a 4 core con hyperthreading (8 core virtuali) tutti al 100% per 1 minuto contribuirebbe con 8 alla media di carico di 1 minuto.

Spesso questi due numeri hanno schemi correlati tra loro, ma non possono essere considerati la stessa cosa. Si può avere un carico elevato con un utilizzo della CPU quasi pari allo 0% (ad esempio quando si hanno molti dati IO bloccati in uno stato di attesa) e si può avere un carico dell'1 e del 100% della CPU, quando si ha un processo a thread singolo che funziona a pieno ritmo. Inoltre, per brevi periodi di tempo è possibile vedere la CPU vicina al 100% ma il carico è ancora inferiore a 1 perché le metriche medie non hanno ancora "recuperato".

Ho visto un server con un carico di oltre 15.000 (sì, non è un errore di battitura) e una percentuale di CPU vicina allo 0%. È successo perché una condivisione Samba aveva dei problemi e molti client hanno iniziato a rimanere bloccati in uno stato di attesa IO. È probabile che se si riscontra un numero di carico regolarmente elevato senza una corrispondente attività della CPU, si abbia un problema di archiviazione di qualche tipo. Nelle macchine virtuali questo può anche significare che ci sono altre macchine virtuali che competono pesantemente per le risorse di storage sullo stesso host della macchina virtuale.

Un carico elevato non è necessariamente una cosa negativa, nella maggior parte dei casi significa solo che il sistema è utilizzato al massimo della sua capacità o forse è al di là della sua capacità di tenere il passo (se il numero di carico è superiore al numero di core del processore). In un posto in cui ero un sysadmin, c'era qualcuno che osservava la media del carico sul sistema primario più da vicino di quanto facesse Nagios. Quando il carico era elevato, mi chiamavano 24 ore su 24, 7 giorni su 7, più velocemente di quanto si possa dire SMTP. La maggior parte delle volte non c'era nulla di sbagliato, ma associavano il numero di carico a qualcosa di sbagliato e lo controllavano come un falco. Dopo aver controllato, di solito la mia risposta era che il sistema stava semplicemente facendo il suo lavoro. Naturalmente si trattava dello stesso posto in cui il carico superava i 15.000 (non lo stesso server, però), quindi a volte significa che qualcosa non va. È necessario considerare lo scopo del sistema. Se è un cavallo di battaglia, allora aspettatevi che il carico sia naturalmente elevato.

Soluzione 2:

Il carico è un numero molto ingannevole. Prendetelo con un grano di sale.

Se si generano molti task in rapida successione che si completano molto velocemente, il numero di processi nella coda di esecuzione è troppo piccolo per registrare il carico (il kernel conta il carico una volta ogni cinque secondi).

Considerate questo esempio, sul mio host che ha 8 core logici, questo script python registrerà un grande utilizzo della CPU in cima (circa l'85%), ma quasi nessun carico.

import os, sys

while True:
  for j in range(8):
    parent = os.fork()
    if not parent:
      n = 0
      for i in range(10000):
        n += 1
      sys.exit(0)
  for j in range(8):
    os.wait()

Un'altra implementazione, questa, evita wait in gruppi di 8 (il che falserebbe il test). In questo caso il genitore cerca sempre di mantenere il numero di figli pari al numero di CPU attive, per cui sarà molto più trafficato del primo metodo e, si spera, più accurato.

/* Compile with flags -O0 */
#include 
#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 

#define ITERATIONS 50000

int maxchild = 0;
volatile int numspawned = 0;

void childhandle(
    int signal)
{
  int stat;
  /* Handle all exited children, until none are left to handle */
  while (waitpid(-1, &stat, WNOHANG) > 0) {
    numspawned--;
  }
}

/* Stupid task for our children to do */
void do_task(
    void)
{
  int i,j;
  for (i=0; i < ITERATIONS; i++)
    j++;
  exit(0);
}

int main() {
  pid_t pid;

  struct sigaction act;
  sigset_t sigs, old;

  maxchild = sysconf(_SC_NPROCESSORS_ONLN);

  /* Setup child handler */
  memset(&act, 0, sizeof(act));
  act.sa_handler = childhandle;
  if (sigaction(SIGCHLD, &act, NULL) < 0)
    err(EXIT_FAILURE, "sigaction");

  /* Defer the sigchild signal */
  sigemptyset(&sigs);
  sigaddset(&sigs, SIGCHLD);
  if (sigprocmask(SIG_BLOCK, &sigs, &old) < 0)
    err(EXIT_FAILURE, "sigprocmask");

  /* Create processes, where our maxchild value is not met */
  while (1) {
    while (numspawned < maxchild) {
      pid = fork();
      if (pid < 0)
        err(EXIT_FAILURE, "fork");

      else if (pid == 0) /* child process */
        do_task();
      else               /* parent */
        numspawned++;
    }
    /* Atomically unblocks signal, handler then picks it up, reblocks on finish */
    if (sigsuspend(&old) < 0 && errno != EINTR)
      err(EXIT_FAILURE, "sigsuspend");
  }
}

Il motivo di questo comportamento è che l'algoritmo passa più tempo a creare processi figli che a eseguire l'attività effettiva (contando fino a 10000). I task non ancora creati non possono essere conteggiati nello stato "eseguibile", ma occuperanno la %sys del tempo di CPU quando vengono generati.

Quindi, nel vostro caso la risposta potrebbe essere che qualsiasi lavoro venga svolto genera un gran numero di task in rapida successione (thread o processi).


Soluzione 3:

Se la media del carico non aumenta di molto, significa che le specifiche hardware e la natura dei task da elaborare garantiscono un buon throughput complessivo, evitando di accumularli nella coda dei task per un po' di tempo.

Se ci fosse un fenomeno di contesa, perché ad esempio la complessità media dei task è troppo alta o il tempo medio di elaborazione dei task richiede troppi cicli della CPU, allora sì, la media del carico aumenterebbe.

AGGIORNAMENTO :

Forse non è chiaro nella mia risposta originale, quindi lo chiarisco ora:

La formula esatta del calcolo della media di carico è : loadvg = tasks running + tasks waiting (for cores) + tasks blocked.

È sicuramente possibile avere un buon throughput e avvicinarsi a una media di carico di 24, ma senza penalizzare il tempo di elaborazione dei task. D'altra parte, si possono anche avere 2-4 task periodici che non si completano abbastanza velocemente, e allora si vedrà crescere il numero di task in attesa (per i cicli della CPU) e alla fine si raggiungerà una media di carico elevata. Un'altra cosa che può accadere è che i task che eseguono operazioni di I/O sincrone in sospeso blocchino un core, riducendo il throughput e facendo crescere la coda dei task in attesa (in questo caso si può vedere il valore iowait cambiare la metrica)


Soluzione 4:

Sebbene la risposta di Matthew Ife sia stata molto utile e ci abbia portato nella giusta direzione, non era esattamente la causa del comportamento nel nostro caso. Nel nostro caso abbiamo un'applicazione Java multifiletto che utilizza il pooling dei thread, per cui non viene svolto alcun lavoro di creazione dei task effettivi.

Tuttavia, il lavoro effettivo che i thread svolgono è di breve durata e comprende attese di IO o di sincronizzazione. Come menzionato da Matthew nella sua risposta, la media del carico è campionata dal sistema operativo, quindi le attività di breve durata possono essere perse.

Ho realizzato un programma Java che riproduce il comportamento. La seguente classe Java genera un utilizzo della CPU del 28% (650% impilato) su uno dei nostri server. Durante l'esecuzione, la media del carico è di circa 1,3. La chiave è la sleep() all'interno del thread, senza la quale il calcolo del carico è corretto.

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MultiThreadLoad {

    private ThreadPoolExecutor e = new ThreadPoolExecutor(200, 200, 0l, TimeUnit.SECONDS,
            new ArrayBlockingQueue(1000), new ThreadPoolExecutor.CallerRunsPolicy());

    public void load() {
        while (true) {
            e.execute(new Runnable() {

                @Override
                public void run() {
                    sleep100Ms();
                    for (long i = 0; i < 5000000l; i++)
                        ;
                }

                private void sleep100Ms() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    public static void main(String[] args) {
        new MultiThreadLoad().load();
    }

}

Per riassumere, la teoria è che i thread nelle nostre applicazioni vanno molto in idle e poi eseguono lavori di breve durata, per cui i task non vengono campionati correttamente dal calcolo della media del carico.


Soluzione 5:

La media del carico include i task che sono bloccati sull'IO del disco, quindi si può facilmente avere un utilizzo della cpu pari a zero e una media del carico di 10 solo avendo 10 task che cercano tutti di leggere da un disco molto lento. È quindi comune che un server molto trafficato inizi a fare thrash sul disco e che tutte le ricerche causino molti task bloccati, facendo salire la media del carico, mentre l'utilizzo della cpu diminuisce, poiché tutti i task sono bloccati sul disco.

Ricorda che ti diamo il privilegio di aggiungere un vero giudizio se hai trovato il tuo sconosciuto.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.