Esta es una pregunta bastante interesante, así que permítanme preparar la escena. Trabajo en el Museo Nacional de Computación y acabamos de conseguir poner en funcionamiento una supercomputadora Cray Y-MP EL de 1992, ¡y realmente queremos ver qué tan rápido puede ir!
Decidimos que la mejor manera de hacer esto era escribir un programa C simple que calcule los números primos y muestre cuánto tiempo tomó hacerlo, luego ejecute el programa en una computadora de escritorio moderna y rápida y compare los resultados.
Rápidamente se nos ocurrió este código para contar números primos:
#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);
}
Lo cual en nuestra computadora portátil de doble núcleo con Ubuntu (The Cray ejecuta UNICOS), funcionó perfectamente, obteniendo el 100% de uso de la CPU y tomando aproximadamente 10 minutos más o menos. Cuando llegué a casa, decidí probarlo en mi PC de juegos moderna de núcleo hexagonal, y aquí es donde tenemos nuestros primeros problemas.
Primero adapté el código para ejecutarlo en Windows, ya que eso es lo que estaba usando la PC para juegos, pero me entristeció descubrir que el proceso solo obtenía alrededor del 15% de la potencia de la CPU. Pensé que Windows debía ser Windows, así que arranqué en un Live CD de Ubuntu pensando que Ubuntu permitiría que el proceso se ejecutara con todo su potencial como lo había hecho antes en mi computadora portátil.
¡Sin embargo, solo obtuve un 5% de uso! Entonces, mi pregunta es, ¿cómo puedo adaptar el programa para que se ejecute en mi máquina de juego en Windows 7 o en Linux en vivo al 100% de uso de la CPU? Otra cosa que sería genial pero no necesaria es si el producto final puede ser un .exe que pueda distribuirse y ejecutarse fácilmente en máquinas con Windows.
¡Muchas gracias!
PD: Por supuesto, este programa no funcionó realmente con los procesadores especializados Crays 8, y ese es otro problema ... Si sabe algo sobre cómo optimizar el código para que funcione en las supercomputadoras Cray de los 90, ¡díganos también!
Respuestas:
Si desea un 100% de CPU, debe usar más de 1 núcleo. Para hacer eso, necesita varios hilos.
Aquí hay una versión paralela que usa OpenMP:
Tuve que aumentar el límite para
1000000
que tardara más de 1 segundo en mi máquina.#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; }
Salida:
Aquí está su CPU al 100%:
fuente
Está ejecutando un proceso en una máquina de varios núcleos, por lo que solo se ejecuta en un núcleo.
La solución es bastante fácil, ya que solo está tratando de vincular el procesador: si tiene N núcleos, ejecute su programa N veces (en paralelo, por supuesto).
Ejemplo
Aquí hay un código que ejecuta los
NUM_OF_CORES
tiempos de su programa en paralelo. Es el código POSIXy, lo usafork
, por lo que debe ejecutarlo en Linux. Si lo que estoy leyendo sobre Cray es correcto, podría ser más fácil portar este código que el código OpenMP en la otra respuesta.#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; }
Salida
$ ./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
fuente
UNICOS
parece que es algo similar a Unix (Wikipedia lo hace pensar de todos modos), así que probablemente lo haya hechofork()
. Deberías aprender a usar eso, creo.Su algoritmo para generar números primos es muy ineficiente. Compararlo con primegen que genera los primos 50847534 hasta 1000000000 en solo 8 segundos en un Pentium II-350.
Para consumir todas las CPU fácilmente, podría resolver un vergonzosamente paralelo , por ejemplo, calcular el conjunto de Mandelbrot o usar la programación genética para pintar Mona Lisa en múltiples subprocesos (procesos).
Otro enfoque es tomar un programa de referencia existente para la supercomputadora Cray y portarlo a una PC moderna.
fuente
La razón por la que obtiene un 15% en un procesador de núcleo hexadecimal es porque su código usa 1 núcleo al 100%. 100/6 = 16.67%, que usando un promedio móvil con programación de procesos (su proceso se estaría ejecutando con prioridad normal) podría reportarse fácilmente como 15%.
Por lo tanto, para usar el 100% de la CPU, necesitaría usar todos los núcleos de su CPU: inicie 6 rutas de código de ejecución paralela para una CPU de núcleo hexadecimal y tenga esta escala hasta la cantidad de procesadores que tenga su máquina Cray :)
fuente
También sé muy consciente de cómo está cargando la CPU. Una CPU puede realizar muchas tareas diferentes, y aunque muchas de ellas se informarán como "cargando la CPU al 100%", es posible que cada una use el 100% de diferentes partes de la CPU. En otras palabras, es muy difícil comparar el rendimiento de dos CPU diferentes, y especialmente dos arquitecturas de CPU diferentes. La ejecución de la tarea A puede favorecer a una CPU sobre otra, mientras que la ejecución de la tarea B puede ser fácilmente al revés (ya que las dos CPU pueden tener diferentes recursos internamente y pueden ejecutar código de manera muy diferente).
Esta es la razón por la que el software es tan importante como el hardware para que las computadoras funcionen de manera óptima. De hecho, esto también es muy cierto para las "supercomputadoras".
Una medida del rendimiento de la CPU podría ser instrucciones por segundo, pero, de nuevo, las instrucciones no se crean de la misma manera en diferentes arquitecturas de CPU. Otra medida podría ser el rendimiento de E / S de la caché, pero la infraestructura de la caché tampoco es igual. Entonces, una medida podría ser la cantidad de instrucciones por vatio utilizadas, ya que la entrega y disipación de energía suelen ser un factor limitante al diseñar una computadora de clúster.
Entonces, su primera pregunta debería ser: ¿Qué parámetro de rendimiento es importante para usted? ¿Qué quieres medir? Si desea ver qué máquina obtiene la mayor cantidad de FPS de Quake 4, la respuesta es fácil; su plataforma de juegos lo hará, ya que Cray no puede ejecutar ese programa en absoluto ;-)
Saludos, Steen
fuente
TLDR; La respuesta aceptada es ineficiente e incompatible. El siguiente algoritmo funciona 100x más rápido.
El compilador gcc disponible en MAC no se puede ejecutar
omp
. Tuve que instalar llvm(brew install llvm )
. Pero no vi que la CPU inactiva se estuviera cayendo mientras se ejecutaba la versión OMP.Aquí hay una captura de pantalla mientras se estaba ejecutando la versión OMP.
Alternativamente, utilicé el hilo POSIX básico, que se puede ejecutar con cualquier compilador c y vi que casi toda la CPU se agotó cuando
nos of thread
=no of cores
= 4 (MacBook Pro, Intel Core i5 de 2,3 GHz). Aquí está el programa:#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #define NUM_THREADS 10 #define THREAD_LOAD 100000 using namespace std; struct prime_range { int min; int max; int total; }; void* findPrime(void *threadarg) { int i, primes = 0; struct prime_range *this_range; this_range = (struct prime_range *) threadarg; int minLimit = this_range -> min ; int maxLimit = this_range -> max ; int flag = false; while (minLimit <= maxLimit) { i = 2; int lim = ceil(sqrt(minLimit)); while (i <= lim) { if (minLimit % i == 0){ flag = true; break; } i++; } if (!flag){ primes++; } flag = false; minLimit++; } this_range ->total = primes; pthread_exit(NULL); } int main (int argc, char *argv[]) { struct timespec start, finish; double elapsed; clock_gettime(CLOCK_MONOTONIC, &start); pthread_t threads[NUM_THREADS]; struct prime_range pr[NUM_THREADS]; int rc; pthread_attr_t attr; void *status; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(int t=1; t<= NUM_THREADS; t++){ pr[t].min = (t-1) * THREAD_LOAD + 1; pr[t].max = t*THREAD_LOAD; rc = pthread_create(&threads[t], NULL, findPrime,(void *)&pr[t]); if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } int totalPrimesFound = 0; // free attribute and wait for the other threads pthread_attr_destroy(&attr); for(int t=1; t<= NUM_THREADS; t++){ rc = pthread_join(threads[t], &status); if (rc) { printf("Error:unable to join, %d" ,rc); exit(-1); } totalPrimesFound += pr[t].total; } clock_gettime(CLOCK_MONOTONIC, &finish); elapsed = (finish.tv_sec - start.tv_sec); elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0; printf("This machine calculated all %d prime numbers under %d in %lf seconds\n",totalPrimesFound, NUM_THREADS*THREAD_LOAD, elapsed); pthread_exit(NULL); }
Observe cómo se agota toda la CPU:
PD: si aumenta el número de subprocesos, el uso real de la CPU disminuirá (intente hacer que el número de subprocesos sea = 20.) ya que el sistema usa más tiempo en el cambio de contexto que la computación real.
Por cierto, mi máquina no es tan robusta como @mystical (respuesta aceptada). Pero mi versión con subprocesos POSIX básicos funciona mucho más rápido que OMP. Aquí está el resultado:
PS Aumente la carga de subprocesos a 2,5 millones para ver el uso de la CPU, ya que se completa en menos de un segundo.
fuente
Intente paralelizar su programa usando, por ejemplo, OpenMP. Es un marco muy sencillo y eficaz para la realización de programas paralelos.
fuente
Para una mejora rápida en un núcleo, elimine las llamadas al sistema para reducir el cambio de contexto. Elimina estas líneas:
system("clear"); printf("%d prime numbers calculated\n",primes);
El primero es particularmente malo, ya que generará un nuevo proceso en cada iteración.
fuente
Simplemente intente comprimir y descomprimir un archivo grande, nada como operaciones pesadas de E / S puede usar cpu.
fuente