80 votes

Comment obtenir un taux d'utilisation du CPU de 100% pour un programme C

C'est une question très intéressante, alors laissez-moi vous présenter le contexte. Je travaille au Musée national de l'informatique, et nous venons de réussir à faire fonctionner un super ordinateur Cray Y-MP EL datant de 1992, et nous voulons vraiment voir à quelle vitesse il peut aller !

Nous avons décidé que la meilleure façon de procéder était d'écrire un simple programme en C qui calculerait les nombres premiers et montrerait le temps nécessaire pour le faire, puis d'exécuter le programme sur un PC de bureau moderne et rapide et de comparer les résultats.

Nous avons rapidement trouvé ce code pour compter les nombres premiers :

#include <stdio.h>
#include <time.h>

void main() {
    clock_t start, end;
    double runTime;
    start = clock();
    int i, num = 1, primes = 0;

    while (num <= 1000) { 
        i = 2; 
        while (i <= num) { 
            if(num % i == 0)
                break;
            i++; 
        }
        if (i == num)
            primes++;

        system("clear");
        printf("%d prime numbers calculated\n",primes);
        num++;
    }

    end = clock();
    runTime = (end - start) / (double) CLOCKS_PER_SEC;
    printf("This machine calculated all %d prime numbers under 1000 in %g seconds\n", primes, runTime);
}

Ce qui, sur notre ordinateur portable double cœur fonctionnant sous Ubuntu (le Cray fonctionne sous UNICOS), a fonctionné parfaitement, avec une utilisation à 100 % de l'unité centrale et en une dizaine de minutes. En rentrant chez moi, j'ai décidé de l'essayer sur mon PC de jeu moderne à six cœurs, et c'est là que nous rencontrons nos premiers problèmes.

J'ai d'abord adapté le code pour qu'il fonctionne sous Windows, puisque c'est ce qu'utilisait le PC de jeu, mais j'ai été attristé de constater que le processus n'utilisait qu'environ 15 % de la puissance du CPU. Je me suis dit que Windows devait être Windows, alors j'ai démarré sur un Live CD d'Ubuntu en pensant qu'Ubuntu permettrait au processus de fonctionner avec tout son potentiel comme il l'avait fait plus tôt sur mon ordinateur portable.

Cependant, je n'ai obtenu que 5% d'utilisation ! Ma question est donc la suivante : comment puis-je adapter le programme pour qu'il fonctionne sur ma machine de jeu sous Windows 7 ou sous Linux en direct avec une utilisation de 100 % du processeur ? Une autre chose qui serait formidable, mais pas nécessaire, est que le produit final puisse être un .exe qui pourrait être facilement distribué et exécuté sur les machines Windows.

Merci beaucoup !

P.S. Bien sûr, ce programme ne fonctionnait pas vraiment avec les processeurs spécialisés Crays 8, et c'est un tout autre problème... Si vous savez comment optimiser un code pour qu'il fonctionne sur les super ordinateurs Cray des années 90, faites-nous signe !

1 votes

Avez-vous essayé le multithreading ?

0 votes

Votre PC de jeu était-il multi-core ?

8 votes

Je ne peux pas croire qu'il n'y a pas de unicos tag. ;)

81voto

Mysticial Points 180300

Si vous voulez 100% de CPU, vous devez utiliser plus d'un cœur. Pour ce faire, vous avez besoin de plusieurs threads.

Voici une version parallèle utilisant OpenMP :

J'ai dû augmenter la limite à 1000000 pour que ça prenne plus d'une seconde sur ma machine.

#include <stdio.h>
#include <time.h>
#include <omp.h>

int main() {
    double start, end;
    double runTime;
    start = omp_get_wtime();
    int num = 1,primes = 0;

    int limit = 1000000;

#pragma omp parallel for schedule(dynamic) reduction(+ : primes)
    for (num = 1; num <= limit; num++) { 
        int i = 2; 
        while(i <= num) { 
            if(num % i == 0)
                break;
            i++; 
        }
        if(i == num)
            primes++;
//      printf("%d prime numbers calculated\n",primes);
    }

    end = omp_get_wtime();
    runTime = end - start;
    printf("This machine calculated all %d prime numbers under %d in %g seconds\n",primes,limit,runTime);

    return 0;
}

Sortie :

Cette machine a calculé les 78498 nombres premiers inférieurs à 1000000 en 29,753 secondes.

Voici votre CPU à 100% :

enter image description here

0 votes

Wow, ça ne t'a pas pris longtemps ! J'ai oublié de dire que la valeur finale que nous utiliserons est 1.000.000 1000 était pour le test (qui comme vous l'avez vu était juste stupide !) Je vais tester cela maintenant, merci beaucoup !

0 votes

En fonction de votre compilateur, il y a un drapeau de compilateur que vous devez activer pour obtenir le support OpenMP.

0 votes

Et on peut aussi utiliser une fonction très utile omp_get_wtime() pour mesurer le temps.

24voto

cha0site Points 5675

Vous exécutez un processus sur une machine à plusieurs cœurs - il ne fonctionne donc que sur un seul cœur.

La solution est assez simple, puisque vous essayez simplement d'évaluer le processeur - si vous avez N cœurs, exécutez votre programme N fois (en parallèle, bien sûr).

Exemple

Voici du code qui exécute votre programme NUM_OF_CORES fois en parallèle. C'est du code POSIXy - il utilise fork - donc vous devriez l'exécuter sous Linux. Si ce que je lis sur la Cray est correct, il pourrait être plus facile de porter ce code que le code OpenMP dans l'autre réponse.

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define NUM_OF_CORES 8
#define MAX_PRIME 100000

void do_primes()
{
    unsigned long i, num, primes = 0;
    for (num = 1; num <= MAX_PRIME; ++num) {
        for (i = 2; (i <= num) && (num % i != 0); ++i);
        if (i == num)
            ++primes;
    }
    printf("Calculated %d primes.\n", primes);
}

int main(int argc, char ** argv)
{
    time_t start, end;
    time_t run_time;
    unsigned long i;
    pid_t pids[NUM_OF_CORES];

    /* start of test */
    start = time(NULL);
    for (i = 0; i < NUM_OF_CORES; ++i) {
        if (!(pids[i] = fork())) {
            do_primes();
            exit(0);
        }
        if (pids[i] < 0) {
            perror("Fork");
            exit(1);
        }
    }
    for (i = 0; i < NUM_OF_CORES; ++i) {
        waitpid(pids[i], NULL, 0);
    }
    end = time(NULL);
    run_time = (end - start);
    printf("This machine calculated all prime numbers under %d %d times "
           "in %d seconds\n", MAX_PRIME, NUM_OF_CORES, run_time);
    return 0;
}

Sortie

$ ./primes 
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
Calculated 9592 primes.
This machine calculated all prime numbers under 100000 8 times in 8 seconds

0 votes

Ah un peu comme lorsque vous avez besoin d'exécuter Prime95, vous avez plusieurs instances de celui-ci... Il y a sûrement un moyen pour un processus d'utiliser plusieurs cœurs ? Comme le font les programmes de craquage de hash.

0 votes

Un processus pourrait utiliser des threads pour faire du multitraitement, mais je ne pense pas que ce soit ce que vous vouliez dire puisqu'un thread est presque un processus distinct dans ce contexte. Ce dont nous parlons réellement ici, ce sont des "têtes d'exécution", qu'il s'agisse de threads ou de processus. Donc, non, il n'y a pas de moyen de faire fonctionner un programme à un seul thread sur plusieurs cœurs, vous devez le réécrire. Et parfois, c'est vraiment dur. Et parfois, c'est même impossible.

0 votes

Je suppose que ce ne sera pas aussi difficile que de faire fonctionner le programme pour la Cray. Étant donné que je suis assez nouveau dans ce domaine (ce qui m'a trahi :P), où serait un bon endroit pour commencer ?

7voto

J.F. Sebastian Points 102961

nous voulons vraiment voir à quelle vitesse il peut aller !

Votre algorithme pour générer des nombres premiers est très inefficace. Comparez-le à primegen qui génère les 50847534 nombres premiers jusqu'à 1000000000 en seulement 8 secondes sur un Pentium II-350.

Pour consommer facilement toutes les unités centrales, on peut résoudre un problème de type problème embarrassant de parallélisme par exemple, calculer ensemble de Mandelbrot ou utiliser programmation génétique pour peindre la Joconde dans plusieurs threads (processus).

Une autre approche consiste à prendre un programme de référence existant pour le superordinateur Cray et à le porter sur un PC moderne.

1 votes

Le fait que l'algorithme soit inefficace n'a pas d'importance, car le but n'est pas de calculer les nombres premiers, mais d'effectuer une tâche génériquement difficile et de voir à quel point il est meilleur ou pire qu'un ordinateur de bureau moderne. Un algorithme efficace ne ferait que rendre cette comparaison plus difficile, et pourrait même ruiner les résultats s'il est si bon qu'il tire délibérément parti des caractéristiques/manques des processeurs modernes.

5voto

C.. Points 10739

La raison pour laquelle vous obtenez 15 % sur un processeur à six cœurs est que votre code utilise un cœur à 100 %. 100/6 = 16,67%, ce qui, en utilisant une moyenne mobile avec planification des processus (votre processus serait exécuté avec une priorité normale) pourrait facilement être rapporté comme 15%.

Par conséquent, pour utiliser 100 % du processeur, il faudrait utiliser tous les cœurs de votre processeur - lancer 6 chemins de code d'exécution parallèles pour un processeur à six cœurs et faire en sorte que cela s'étende jusqu'au nombre de processeurs de votre machine Cray :)

0 votes

Le problème est le suivant : comment puis-je obtenir un chiffre précis de la vitesse de chacune des machines ? De plus, le Cray a des "processeurs vectoriels" apparemment, donc il faut beaucoup plus de travail pour le faire fonctionner correctement.

0 votes

Je ne sais pas. Probablement des différences dans les processus d'ordonnancement.

2voto

Steen Schmidt Points 11

Soyez aussi très conscient comment vous chargez le CPU. Un processeur peut effectuer un grand nombre de tâches différentes et, bien que la plupart d'entre elles soient signalées comme "chargeant le processeur à 100%", elles peuvent chacune utiliser 100% de différentes parties du processeur. En d'autres termes, il est très difficile de comparer les performances de deux CPU différents, et surtout de deux architectures de CPU différentes. L'exécution d'une tâche A peut favoriser un CPU par rapport à un autre, tandis que l'exécution d'une tâche B peut facilement être l'inverse (puisque les deux CPU peuvent avoir des ressources internes différentes et peuvent exécuter du code très différemment).

C'est la raison pour laquelle les logiciels sont tout aussi importants que le matériel pour assurer le fonctionnement optimal des ordinateurs. C'est également vrai pour les "superordinateurs".

Une mesure des performances du CPU pourrait être les instructions par seconde, mais là encore, les instructions ne sont pas créées égales sur les différentes architectures de CPU. Une autre mesure pourrait être la performance du cache IO, mais l'infrastructure du cache n'est pas égale non plus. Enfin, on pourrait mesurer le nombre d'instructions par watt utilisé, car l'alimentation et la dissipation de l'énergie sont souvent un facteur limitant lors de la conception d'un ordinateur en grappe.

Votre première question devrait donc être : Quel paramètre de performance est important pour vous ? Que voulez-vous mesurer ? Si vous voulez voir quelle machine obtient le plus de FPS avec Quake 4, la réponse est simple : votre machine de jeu, car le Cray ne peut pas du tout exécuter ce programme ;-)

Santé, Steen

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X