Cómo obtener el 100% de uso de CPU de un progtwig C

Esta es una pregunta bastante interesante, así que déjame establecer la escena. Trabajo en el Museo Nacional de Computación, y recién hemos logrado obtener una súper computadora Cray Y-MP EL desde 1992, ¡y realmente queremos ver qué tan rápido puede llegar!

Decidimos que la mejor manera de hacerlo era escribir un progtwig simple de C que calculó los números primos y mostrar cuánto tiempo tomó hacerlo, luego ejecutar el progtwig en una computadora de escritorio moderna y rápida y comparar los resultados.

Rápidamente creamos este código para contar números primos:

#include  #include  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 dual con Ubuntu (The Cray ejecuta UNICOS), funcionó a la perfección, 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 modernos de núcleo hexadecimal, y aquí es donde obtenemos nuestros primeros problemas.

Primero adapté el código para ejecutar en Windows, ya que eso es lo que estaba usando la PC de juegos, pero me entristeció descubrir que el proceso solo obtenía aproximadamente el 15% de la potencia de la CPU. Pensé que Windows debe 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 anteriormente en mi computadora portátil.

¡Sin embargo, solo obtuve un 5% de uso! Entonces mi pregunta es, ¿cómo puedo adaptar el progtwig para que se ejecute en mi máquina de juego en Windows 7 o Linux en vivo al 100% de utilización de la CPU? Otra cosa que sería genial pero no necesaria es si el producto final puede ser un .exe que pueda distribuirse fácilmente y ejecutarse en máquinas con Windows.

¡Muchas gracias!

PD: Por supuesto, este progtwig no funcionó realmente con los procesadores especializados de Crays 8, y ese es otro problema … Si sabes algo sobre la optimización del código para trabajar en las supercomputadoras Cray de los 90, ¡danos un grito también!

Si quieres 100% CPU, necesitas usar más de 1 núcleo. Para hacer eso, necesitas múltiples hilos.

Aquí hay una versión paralela usando OpenMP:

Tuve que boost el límite a 1000000 para que tardara más de 1 segundo en mi máquina.

 #include  #include  #include  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:

Esta máquina calculó todos los 78498 números primos por debajo de 1000000 en 29.753 segundos

Aquí está tu CPU 100%:

enter image description here

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 progtwig N veces (en paralelo, por supuesto).

Ejemplo

Aquí hay un código que ejecuta su progtwig NUM_OF_CORES veces en paralelo. Es un código POSIXy, usa un fork , por lo que debe ejecutarlo en Linux. Si lo que estoy leyendo sobre Cray es correcto, podría ser más fácil ingresar este código que el código OpenMP en la otra respuesta.

 #include  #include  #include  #include  #include  #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 

¡Realmente queremos ver qué tan rápido puede llegar!

Su algoritmo para generar números primos es muy ineficiente. Compáralo con el primógeno que genera los números primos 50847534 hasta 1000000000 en solo 8 segundos en un Pentium II-350.

Para consumir todas las CPU fácilmente, podría resolver un problema embarazosamente paralelo , por ejemplo, calcular Mandelbrot o utilizar la progtwigción genética para pintar Mona Lisa en múltiples hilos (procesos).

Otro enfoque es tomar un progtwig de referencia existente para el superordenador Cray y llevarlo a una PC moderna.

La razón por la que obtienes un 15% en un procesador de núcleo hexagonal es porque tu código usa 1 núcleo al 100%. 100/6 = 16.67%, que usando una media móvil con la progtwigción del proceso (su proceso se estaría ejecutando bajo la prioridad normal) podría reportarse fácilmente como 15%.

Por lo tanto, para usar 100% de CPU, necesitaría usar todos los núcleos de su CPU: inicie 6 rutas de código de ejecución paralelas para una CPU de núcleo hexagonal y tenga esta escala hasta sin importar cuántos procesadores tenga su máquina Cray 🙂

También sé muy consciente de cómo estás cargando la CPU. Una CPU puede hacer muchas tareas diferentes, y aunque muchas de ellas serán reportadas como “cargando la CPU al 100%”, cada una puede usar el 100% de las diferentes partes de la CPU. En otras palabras, es muy difícil comparar dos CPU diferentes para el rendimiento, y especialmente dos architectures 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 recursos internos diferentes y pueden ejecutar código de forma muy diferente).

Esta es la razón por la que el software es tan importante para hacer que las computadoras funcionen de manera óptima como lo es el hardware. Esto también es muy cierto para los “supercomputadores”.

Una medida para el rendimiento de la CPU podría ser instrucciones por segundo, pero de nuevo las instrucciones no se crean iguales en diferentes architectures de CPU. Otra medida podría ser el rendimiento de IO de caché, pero la infraestructura de caché tampoco es igual. Entonces, una medida podría ser el número de instrucciones por vatio utilizado, ya que la entrega y la disipación de potencia suelen ser un factor limitante al diseñar una computadora en 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; tu plataforma de juego lo hará, ya que Cray no puede ejecutar ese progtwig en absoluto 😉

Saludos, Steen

Intente paralelizar su progtwig usando, por ejemplo, OpenMP. Es un marco muy simple y efectivo para crear progtwigs paralelos.

Para una mejora rápida en un núcleo, elimine las llamadas al sistema para reducir el cambio de contexto. Eliminar 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.

Simplemente intente comprimir y descomprimir un archivo grande, nada como operaciones pesadas de E / S pueden usar la CPU.

TLDR; La respuesta aceptada es a la vez ineficiente e incompatible. Seguir algo funciona 100 veces más rápido.

El comstackdor gcc disponible en MAC no puede ejecutar omp . Tuve que instalar llvm (brew install llvm ) . Pero no vi que la CPU inactiva bajara mientras ejecutaba la versión de OMP.

Aquí hay una captura de pantalla mientras se ejecutaba la versión de OMP. enter image description here

Alternativamente, utilicé el hilo POSIX básico, que se puede ejecutar usando cualquier comstackdor c y se utilizó casi toda la CPU nos of thread cuando nos of thread = no of cores = 4 (MacBook Pro, Intel Core i5 a 2,3 GHz). Aquí está el progtwig –

 #include  #include  #include  #include  #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 consume toda la CPU - enter image description here

PD: si aumenta el número de subprocesos, el uso real de la CPU disminuirá (intente hacer no de subprocesos = 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 carnosa como @mystical (respuesta aceptada). Pero mi versión con enhebrado POSIX básico funciona mucho más rápido que OMP one. Aquí está el resultado:

enter image description here

PS Aumente la carga de hilos a 2,5 millones para ver el uso de la CPU, ya que se completa en menos de un segundo.