Cómo mantener el uso de la CPU al 50% en C (por ejemplo, usando Explorer)
Lo que quiero explicar aquí es que el 50% que se muestra en el administrador de tareas (taskmgr) no significa que la CPU actual realmente esté funcionando a La mitad de la frecuencia La observación cuidadosa muestra que el gráfico de uso de la CPU de taskmgr se actualiza periódicamente. Es decir, durante cada intervalo posterior, el tiempo de actividad de la CPU (ejecución de instrucciones) y el tiempo de inactividad (suspensión) son exactamente iguales, por lo que se puede considerar. que la CPU La tasa de ocupación es del 50%. En la superficie, se puede pensar que la frecuencia de funcionamiento de la CPU ha disminuido (en realidad no es así).
Con la comprensión anterior, puede tener una idea general: primero ejecute un cierto tamaño de código en el programa y luego llame a la función Sleep() para dejar que el hilo cuelgue durante X (milisegundos), y y así sucesivamente, si el tiempo de ejecución del código y el tiempo dedicado a colgar son aproximadamente iguales, entonces, desde una perspectiva de rango, el uso de la CPU es exactamente del 50%. Con esta idea inicial, la dificultad radica en cómo estimar el tiempo que lleva ejecutar el código. Para ejecutar un código de cierto tamaño, la forma más sencilla es escribir un bucle ocupado (es decir, un bucle vacío), como for (. i = 0; i < n; i++); si puede estimar razonablemente el tiempo de ejecución de este código, podrá saber cuánto tiempo debe colgarse.
La razón por la que elegí el bucle ocupado en lugar de otra API es porque no requiere llamar a ninguna instrucción privada (sin io, etc.), por lo que no afectará el tiempo de ejecución del kernel, porque el kernel no puede controlar el tiempo de ejecución de las instrucciones privadas y, para suavizar la curva del 50%, sería mejor obtener una estimación más precisa de cuánto tiempo llevará ejecutarse. Para suavizar la curva del 50%, deberíamos intentar reducir el tiempo que el núcleo no puede controlar. Por lo tanto, los bucles ocupados son las piezas de código más simples y fáciles de estimar.
El siguiente paso es la estimación: analizando for(i = 0; i < n; i++); podemos estimar aproximadamente que cada bucle requiere 5 líneas de código ensamblador
loop:< / p>
mov dx i
añadir dx 1
mov i dx
cmp i n
jmp loop (tengo mucho tiempo Hace tiempo que aprendí a ensamblar, pero se me olvidó cómo escribir la declaración para saltar después de juzgar el tamaño...Hazlo)
Mi CPU es P4 2.4GHz, que es igual a 2.4*10 al potencia de 9 ciclos de reloj por segundo. Recuerdo que cuando estaba estudiando los principios de las microcomputadoras, dije que las CPU modernas pueden ejecutar más de 2 códigos por ciclo de reloj, así que tomaré el promedio de 2, de modo que (2400,000, 000*). 2)/5=960000000 (ciclo/segundos), es decir, la cpu puede ejecutar más de 2 códigos en un segundo, luego tomo el promedio de 2, así que sea (2400000000*2)/5=960000000 (ciclos /segundo), CPU en un segundo, código de CPU que se puede usar en un segundo, luego tomo el promedio de 2. Es decir, la CPU puede ejecutar este bucle 960.000.000 de veces por segundo. Pero no podemos simplemente establecer n = 960000000 y luego Sleep(1000). Debido a que Windows no es un sistema exclusivo en tiempo real, sino un sistema multitarea preventivo, el intervalo de tiempo asignado a un determinado subproceso será reemplazado por otros subprocesos con mayor prioridad, y la frecuencia de actualización de taskmgr también es inferior a 1 s. Si la CPU funciona durante 1 segundo y se suspende durante 1 segundo, es probable que la forma de onda sea irregular, alcanzando primero un pico (superior a >50%) y luego cayendo a una tasa de ocupación muy baja. Así que intenté reducirlo en dos órdenes de magnitud, dejando que n = 9600000 y luego Sleep(10). Utilicé 10 milisegundos porque no es ni demasiado grande ni demasiado pequeño, mientras que 1 milisegundo provocaría que los subprocesos se activaran y se colgaran con frecuencia, aumentando así la incertidumbre del tiempo del kernel.
Puede obtener un código como este:
#include
int main()
{
for(;
{
for(i = 0; i < 9600000; i++);
Dormir( 10);
} p >
return 0;
}
Compile, intente cerrar otras aplicaciones, detenga los servicios que se pueden detener, luego ejecute, ¡Bingo! En mi máquina ha mostrado La CPU. el uso es aproximadamente del 50%, pero todavía hay una pequeña cantidad de fluctuación. En este momento, puede ajustar el tamaño de n adecuadamente y luego intentar ejecutarlo varias veces para obtener un resultado relativamente fluido.
Para verificación. ¿Mi cálculo es correcto? Lo probé en algunas máquinas más y descubrí que los resultados serán diferentes para diferentes CPU. En una CPU HT p4 de 3,2 ghz, desactivé la función HT y obtuve 7680000 6 ms. un resultado relativamente bueno y también descubrí un fenómeno interesante, es decir, después de activar la función HT, no es necesaria la función Sleep (). Simplemente escribir un bucle infinito puede hacer que la CPU funcione a aproximadamente el 50%. Se puede decir que es el método más simple :), pero en Centrino y las CPU de la serie móvil, debido a que la frecuencia de ejecución cambia dinámicamente, el valor estimado logrado es muy diferente de la situación real. Después de que el programa se esté ejecutando, establecer su prioridad en el Administrador de tareas (para reducir la contención) también puede suavizar las formas de onda hasta cierto punto. La visualización del tiempo central muestra que la fluctuación en la forma de onda es causada esencialmente por la actividad central.
De acuerdo con este principio, no es imposible mantener la forma de onda de la CPU en un valor distinto del 50 %. La clave es hacer coincidir el número de ciclos con el tiempo de suspensión y el orden de estos números de ciclo. También es coherente con el tiempo de CPU y la asignación de chips está relacionada con el ciclo de actualización de la forma de onda del sistema de gestión de tareas. No es difícil estimar un valor, aumentarlo o reducirlo y modificarlo un poco para encontrar los dos valores correctos. Además, según este principio, también podemos hacer que la forma de onda de la CPU aparezca en forma de función sin(). Por ejemplo, primero muestree un intervalo de 10' entre 0'~180' para obtener una matriz de valores de función. y luego realice un ciclo ocupado según el valor de la matriz, el número de ciclos y el tiempo de suspensión se convierten y modifican. Esto es ciertamente factible en teoría, pero cuanto mayor es el número de instrucciones, más compleja es y más precisa. de la estimación también disminuirá. Esto es ciertamente posible en teoría, pero cuanto mayor sea el número y la complejidad de las instrucciones, menos precisa será la estimación.
Para comprender el principio de funcionamiento de taskmgr, verifiqué los datos de la API correspondiente y descubrí que las dos funciones QueryPerformanceFrequency() y QueryPerformanceCounter() pueden obtener con precisión el tiempo de ejecución de un fragmento de código, por lo que Pensé que podría construir dos. Para un bucle ocupado idéntico, inicie el programa convirtiendo el número de ciclo y el tiempo de suspensión del primer bucle.
bucle ocupado, primero calcule el tiempo exacto requerido para ejecutarse al comienzo del programa y luego úselo para asignar un valor a la función Sleep (), que también puede lograr una sensación de automatización (no es necesario estimar XD... )
Modifique el programa de la siguiente manera:
#include
int main()
{
int i = 96000000, j = 0;
LARGE_INTEGER frecuencia = {0};
LARGE_INTEGER inicio = {0};
LARGE_INTEGER fin = {0};
DWORD elapse = 0;
QueryPerformanceFrequency(&freq
printf( "freq = %u\n", freq.QuadPart);
QueryPerformanceCounter (&start);
QueryPerformanceCounter(&end);
LONGLONG gastos generales = end.QuadPart - start.QuadPart;
printf("overhead = %d\n ", overhead);
QueryPerformanceCounter(&start);
for (i = 0; i < 9600000; i++);
QueryPerformanceCounter(&end);
LONGLONG temp = (end.QuadPart - start.QuadPart - gastos generales);
printf("temp = %d\n", temp );
elapse = (DWORD)(((doble)temp / (doble)freq.QuadPart))* 1000.0);
printf("elapse = %lf\n" , temp2);
for(;
{
for(i = 0; i < 9600000; i++);
Dormir (elapse);
}
return 0;
}
Sin embargo, el cálculo exacto no parece dar como resultado una solución más fluida. imagen, ya que incluso la resté dos veces. El costo de las llamadas a la API es, quizás, demasiado... De todos modos, los resultados de mis experimentos son bastante aproximados.
Finalmente, hay dos sugerencias:
1. Minimizar la frecuencia y el costo del sueño/despertar. Si el sueño/despertar ocurre con demasiada frecuencia, tendrá un gran impacto.
2. Intente no realizar llamadas al sistema (instrucciones privadas de E/S), porque esto provocará una gran cantidad de tiempo del kernel incontrolable.
3. Debido a las características de contención de la multitarea preventiva, es casi imposible controlar con precisión la curva...
(No sé si esta conclusión es absoluta, si hay una manera de hackear el punto de llamada de la función taskmgr, modificar su valor de retorno y luego mostrar cualquier forma de onda que desees, no tengo el poder para hacer algo tan "genial"...)
4.