Domanda

Riesco a capire come si può scrivere un programma che utilizza più processi o thread: fork () un nuovo processo e utilizzare IPC, oppure creare più thread e utilizzare quel tipo di meccanismi di comunicazione.

Comprendo anche il cambio di contesto. Cioè, con una sola CPU, il sistema operativo pianifica il tempo per ogni processo (e ci sono tonnellate di algoritmi di pianificazione là fuori) e quindi otteniamo l'esecuzione di più processi contemporaneamente.

E ora che abbiamo processori multi-core (o computer multi-processore), potremmo avere due processi in esecuzione contemporaneamente su due core separati.

La mia domanda riguarda l'ultimo scenario: in che modo il kernel controlla su quale core viene eseguito un processo? Quale sistema chiama (in Linux o persino Windows) pianifica un processo su un core specifico?

Il motivo per cui lo sto chiedendo: sto lavorando a un progetto per la scuola in cui stiamo esplorando un argomento recente nel campo dell'informatica - e ho scelto architetture multi-core. Sembra che ci sia un sacco di materiale su come programmare in quel tipo di ambiente (come guardare per deadlock o condizioni di gara) ma non molto sul controllo dei singoli core stessi. Mi piacerebbe poter scrivere alcuni programmi dimostrativi e presentare alcune istruzioni di assemblaggio o codice C all'effetto di & Quot; Vedi, sto eseguendo un ciclo infinito sul 2 ° core, guarda il picco nell'utilizzo della CPU per quel nucleo specifico " ;.

Qualche esempio di codice? O tutorial?

modifica: per chiarimenti - molte persone hanno detto che questo è lo scopo del sistema operativo e che si dovrebbe lasciare che il sistema operativo si occupi di questo. Sono completamente d'accordo! Ma poi quello che chiedo (o cerco di avere un'idea) è ciò che il sistema operativo fa effettivamente per fare questo. Non l'algoritmo di pianificazione, ma più & Quot; una volta scelto un core, quali istruzioni devono essere eseguite per far sì che quel core inizi a recuperare le istruzioni? & Quot;

È stato utile?

Soluzione

Come altri hanno già detto, l'affinità del processore è specifica del sistema operativo . Se vuoi farlo al di fuori dei confini del sistema operativo, ti diverti un sacco e con questo intendo dolore.

Detto questo, altri hanno menzionato SetProcessAffinityMask per Win32. Nessuno ha menzionato il modo del kernel Linux di impostare l'affinità del processore, e quindi lo farò. Devi usare la funzione sched_setaffinity. Ecco un bel tutorial su come.

Altri suggerimenti

Normalmente la decisione su quale core verrà eseguita un'app viene presa dal sistema. Tuttavia, è possibile impostare & Quot; affinity & Quot; per un'applicazione a un core specifico per dire al sistema operativo di eseguire l'app solo su quel core. Normalmente questa non è una buona idea, ma ci sono alcuni rari casi in cui potrebbe avere senso.

Per farlo in Windows, usa il task manager, fai clic con il tasto destro del mouse sul processo e scegli " Imposta l'affinità " ;. Puoi farlo a livello di codice in Windows usando funzioni come SetThreadAffinityMask, SetProcessAffinityMask o SetThreadIdealProcessor.

ETA:

Se sei interessato a come il sistema operativo esegue effettivamente la pianificazione, potresti voler dare un'occhiata a questi link:

Articolo di Wikipedia sul cambio di contesto

Articolo di Wikipedia sulla pianificazione

Pianificazione nel kernel di Linux

Con la maggior parte dei sistemi operativi moderni, il sistema operativo pianifica l'esecuzione di un thread su un core per un breve lasso di tempo. Quando scade il periodo di tempo o il thread esegue un'operazione di I / O che gli fa restituire volontariamente il core, il sistema operativo pianificherà l'esecuzione di un altro thread sul core (se ci sono thread pronti per l'esecuzione). Il thread esattamente programmato dipende dall'algoritmo di pianificazione del sistema operativo.

I dettagli di implementazione di esattamente come si verifica l'interruttore di contesto sono amp & CPU; OS dipendente. Generalmente implica un passaggio alla modalità kernel, il sistema operativo che salva lo stato del thread precedente, carica lo stato del nuovo thread, quindi torna alla modalità utente e riprende il thread appena caricato. L'articolo sul cambio di contesto che ho collegato sopra ha un po 'più di dettagli su questo.

Nulla dice core " ora inizia a eseguire questo processo " ;.

Il core non vede il processo , conosce solo il codice eseguibile e vari livelli di esecuzione e le limitazioni associate alle istruzioni che possono essere eseguite.

All'avvio del computer, per semplicità è attivo solo un core / processore che esegue effettivamente qualsiasi codice. Quindi, se il sistema operativo è in grado di supportare MultiProcessor, attiva altri core con alcune istruzioni specifiche del sistema, molto probabilmente altri core raccolgono dallo stesso punto di altri core e scorrono da lì.

Quindi, ciò che fa lo scheduler è guardare attraverso le strutture interne del sistema operativo (task / processo / coda di thread) e ne seleziona una e la contrassegna come in esecuzione al suo interno. Quindi le altre istanze dello scheduler in esecuzione su altri core non lo toccheranno fino a quando l'attività non sarà di nuovo in stato di attesa (e non contrassegnata come bloccata su core specifico). Dopo che l'attività è stata contrassegnata come in esecuzione, lo scheduler esegue il passaggio all'area utente con l'attività che riprende nel punto in cui è stata precedentemente sospesa.

Tecnicamente non c'è nulla che impedisca ai core di eseguire esattamente lo stesso codice nello stesso momento (e molte funzioni sbloccate lo fanno), ma a meno che il codice non sia scritto per aspettarselo, probabilmente piscia su tutto se stesso.

Lo scenario diventa più strano con modelli di memoria più esotici (sopra assume " solita " spazio di memoria di lavoro singolo lineare) in cui i core non necessariamente vedono tutti la stessa memoria e potrebbero esserci requisiti per il recupero del codice da le altre frizioni del core, ma è molto più facile da gestire semplicemente mantenendo il compito appuntato al core (l'architettura AFAIK Sony PS3 con SPU è così).

Il progetto OpenMPI ha un libreria per impostare l'affinità del processore su Linux in modo portatile.

Qualche tempo fa, l'ho usato in un progetto e ha funzionato bene.

Avvertenza: ricordo vagamente che c'erano dei problemi nello scoprire come il sistema operativo numera i core. L'ho usato in un sistema CPU 2 Xeon con 4 core ciascuno.

Uno sguardo a cat /proc/cpuinfo potrebbe essere d'aiuto. Sulla scatola che ho usato, è piuttosto strano. L'output ridotto è alla fine.

Evidentemente, i nuclei con numerazione uniforme sono nella prima CPU e i nuclei con numeri dispari nella seconda CPU. Tuttavia, se ricordo bene, c'era un problema con le cache. Su questi processori Intel Xeon, due core su ciascuna CPU condividono le loro cache L2 (non ricordo se il processore ha una cache L3). Penso che i processori virtuali 0 e 2 abbiano condiviso una cache L2, 1 e 3 condivisa una, 4 e 6 condivisa una e 5 e 7 condivisa una.

A causa di questa stranezza (1,5 anni fa non riuscivo a trovare alcuna documentazione sulla numerazione dei processi in Linux), starei attento a fare questo tipo di tuning a basso livello. Tuttavia, ci sono chiaramente alcuni usi. Se il codice viene eseguito su alcuni tipi di macchine, potrebbe valere la pena eseguire questo tipo di ottimizzazione. Un'altra applicazione potrebbe essere in un linguaggio specifico del dominio come StreamIt dove il compilatore potrebbe farlo lavoro sporco e calcolo di un programma intelligente.

processor       : 0
physical id     : 0
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 1
physical id     : 1
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 2
physical id     : 0
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 3
physical id     : 1
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 4
physical id     : 0
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 5
physical id     : 1
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 6
physical id     : 0
siblings        : 4
core id         : 3
cpu cores       : 4

processor       : 7
physical id     : 1
siblings        : 4
core id         : 3
cpu cores       : 4

Per scoprire il numero di processori invece di usare / proc / cpuinfo basta eseguire:

nproc

Per eseguire un processo su un gruppo di processori specifici:

taskset --cpu-list 1,2 my_command 

dirà che il mio comando può essere eseguito solo su CPU 1 o 2.

Per eseguire un programma su 4 processori che eseguono 4 cose diverse, utilizzare la parametrizzazione. L'argomento del programma gli dice di fare qualcosa di diverso:

for i in `seq 0 1 3`;
do 
  taskset --cpu-list $i my_command $i;
done

Un buon esempio di ciò riguarda 8 milioni di operazioni in un array in modo che 0 a (2mil-1) vada al processore 1, 2mil a (4mil-1) al processore 2 e così via.

Puoi guardare il carico su ogni processo installando htop usando apt-get / yum ed eseguendolo dalla riga di comando:

 htop

Come altri hanno già detto, è controllato dal sistema operativo. A seconda del sistema operativo, può fornire o meno chiamate di sistema che consentono di influire sul core su cui viene eseguito un determinato processo. Tuttavia, di solito dovresti semplicemente lasciare che il sistema operativo esegua il comportamento predefinito. Se disponi di un sistema a 4 core con 37 processi in esecuzione e 34 di questi processi sono inattivi, pianificherà i restanti 3 processi attivi su core separati.

Probabilmente vedrai un aumento di velocità solo giocando con le affinità di base in applicazioni multithread molto specializzate. Ad esempio, supponiamo di avere un sistema con 2 processori dual-core. Supponiamo di avere un'applicazione con 3 thread e che due di essi operino pesantemente sullo stesso set di dati, mentre il terzo thread utilizza un diverso set di dati. In questo caso, trarrai maggiori benefici avendo i due thread che interagiscono sullo stesso processore e il terzo thread sull'altro processore, da allora possono condividere una cache. Il sistema operativo non ha idea di quale memoria debba accedere a ciascun thread, quindi potrebbe non allocare correttamente i thread ai core.

Se sei interessato a come il sistema operativo, leggi pianificazione . I dettagli grintosi del multiprocessing su x86 sono disponibili nel Software Intel 64 e IA-32 Architectures Manuali dello sviluppatore . Il volume 3A, i capitoli 7 e 8 contengono informazioni pertinenti, ma tenere presente che questi manuali sono estremamente tecnici.

Il sistema operativo sa come farlo, non è necessario. Potresti imbatterti in tutti i tipi di problemi se specifichi su quale core eseguire, alcuni dei quali potrebbero effettivamente rallentare il processo. Lascia che il sistema operativo lo capisca, devi solo avviare il nuovo thread.

Ad esempio, se dicessi a un processo di avviarsi su core x, ma core x fosse già sotto carico, sarebbe peggio che se avessi lasciato che il sistema operativo lo gestisse.

Non conosco le istruzioni di montaggio. Ma la funzione API di Windows è SetProcessAffinityMask . Puoi vedere un esempio di qualcosa che ho messo insieme qualche tempo fa per eseguire Picasa su un solo core

Linux sched_setaffinity C esempio minimo eseguibile

In questo esempio, otteniamo l'affinità, la modifichiamo e controlliamo se ha avuto effetto con sched_getcpu() .

#define _GNU_SOURCE
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_affinity() {
    cpu_set_t mask;
    long nproc, i;

    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_getaffinity");
        assert(false);
    } else {
        nproc = sysconf(_SC_NPROCESSORS_ONLN);
        printf("sched_getaffinity = ");
        for (i = 0; i < nproc; i++) {
            printf("%d ", CPU_ISSET(i, &mask));
        }
        printf("\n");
    }
}

int main(void) {
    cpu_set_t mask;

    print_affinity();
    printf("sched_getcpu = %d\n", sched_getcpu());
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_setaffinity");
        assert(false);
    }
    print_affinity();
    /* TODO is it guaranteed to have taken effect already? Always worked on my tests. */
    printf("sched_getcpu = %d\n", sched_getcpu());
    return EXIT_SUCCESS;
}

Compila ed esegui con:

gcc -std=c99 main.c
./a.out

Output di esempio:

sched_getaffinity = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
sched_getcpu = 9
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

Il che significa che:

  • inizialmente, tutti i miei 16 core erano abilitati e il processo funzionava in modo casuale sul core 9 (il decimo)
  • dopo aver impostato l'affinità solo sul primo core, il processo è stato spostato necessariamente sul core 0 (il primo)

È anche divertente eseguire questo programma tramite taskset:

taskset -c 1,3 ./a.out

Che fornisce l'output del modulo:

sched_getaffinity = 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 2
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

e quindi vediamo che ha limitato l'affinità dall'inizio.

Funziona perché l'affinità è ereditata dai processi figlio, che <=> sta biforcando: Come impedire ereditare l'affinità della CPU con il processo fork di bambini?

Testato in Ubuntu 16.04, GitHub up .

x86 bare metal

Se sei così hardcore: Che cosa significa linguaggio assembly multicore assomigliare?

Come Linux lo implementa

Come funziona sched_setaffinity ()?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top