c++ - sjf - ¿Cuánto tiempo tarda la creación y terminación de subprocesos en Windows?
procesos sistemas operativos pdf (2)
He dividido una tarea compleja de procesamiento de matrices en varios subprocesos para aprovechar el procesamiento de múltiples núcleos y estoy viendo grandes beneficios. Actualmente, al inicio de la tarea, creo los subprocesos y luego espero a que terminen a medida que completan su trabajo. Por lo general, estoy creando aproximadamente cuatro veces el número de subprocesos, ya que hay núcleos, ya que cada subproceso puede tardar un tiempo diferente, y tener subprocesos adicionales garantiza que todos los núcleos se mantengan ocupados la mayor parte del tiempo. Me preguntaba si habría una gran ventaja en el rendimiento al crear los subprocesos a medida que se inicia el programa, manteniéndolos inactivos hasta que se requieran, y usándolos cuando comienzo a procesar. En pocas palabras, ¿cuánto tiempo lleva iniciar y finalizar un nuevo hilo por encima y más allá del procesamiento dentro del hilo? Estoy actualmente comenzando los hilos usando
CWinThread *pMyThread = AfxBeginThread(CMyThreadFunc,&MyData,THREAD_PRIORITY_NORMAL);
Normalmente, usaré 32 subprocesos en 8 núcleos en una arquitectura de 64 bits. El proceso en cuestión toma actualmente <1 segundo y se activa cada vez que se actualiza la pantalla. Si el inicio y finalización de un hilo es <1 ms, el retorno no justifica el esfuerzo. Estoy teniendo algunas dificultades para perfilar esto.
Una pregunta relacionada aquí ayuda, pero es un poco vaga para lo que busco. Cualquier comentario apreciado.
Algunos consejos:
- Si tiene muchos elementos de trabajo para procesar (o no hay demasiados, pero tiene que repetir todo el proceso de vez en cuando), asegúrese de usar algún tipo de agrupación de subprocesos. De esta manera, no tendrá que volver a crear los hilos todo el tiempo, y su pregunta original ya no importará: los hilos se crearán una sola vez. Uso la API QueueUserWorkItem directamente (ya que mi aplicación no usa MFC), incluso esa no es demasiado dolorosa. Pero en MFC puede tener instalaciones de nivel superior para aprovechar la agrupación de subprocesos. ( http://support.microsoft.com/kb/197728 )
- Intente seleccionar la cantidad óptima de trabajo para un elemento de trabajo. Por supuesto, esto depende de la característica de su software: ¿se supone que debe ser en tiempo real, o es un número crujiendo en segundo plano? Si no es en tiempo real, una cantidad demasiado pequeña de trabajo por elemento de trabajo puede perjudicar el rendimiento: al aumentar la proporción de gastos generales de la distribución del trabajo entre los subprocesos.
- Dado que las configuraciones de hardware pueden ser muy diferentes, si sus usuarios finales pueden tener varias máquinas, puede incluir algunas rutinas de calibración de forma asíncrona durante el inicio del software, de modo que pueda estimar cuánto tiempo lleva una determinada operación. El resultado de la calibración puede ser una entrada para una mejor configuración de tamaño de trabajo más adelante para los cálculos reales.
Escribí esto hace bastante tiempo cuando tenía la misma pregunta básica (junto con otra que será obvia). Lo he actualizado para mostrar un poco más sobre no solo cuánto tiempo se tarda en crear subprocesos, sino también cuánto tardan los subprocesos en comenzar a ejecutarse:
#include <windows.h>
#include <iostream>
#include <time.h>
#include <vector>
const int num_threads = 32;
const int switches_per_thread = 100000;
DWORD __stdcall ThreadProc(void *start) {
QueryPerformanceCounter((LARGE_INTEGER *) start);
for (int i=0;i<switches_per_thread; i++)
Sleep(0);
return 0;
}
int main(void) {
HANDLE threads[num_threads];
DWORD junk;
std::vector<LARGE_INTEGER> start_times(num_threads);
LARGE_INTEGER l;
QueryPerformanceCounter(&l);
clock_t create_start = clock();
for (int i=0;i<num_threads; i++)
threads[i] = CreateThread(NULL,
0,
ThreadProc,
(void *)&start_times[i],
0,
&junk);
clock_t create_end = clock();
clock_t wait_start = clock();
WaitForMultipleObjects(num_threads, threads, TRUE, INFINITE);
clock_t wait_end = clock();
double create_millis = 1000.0 * (create_end - create_start) / CLOCKS_PER_SEC / num_threads;
std::cout << "Milliseconds to create thread: " << create_millis << "/n";
double wait_clocks = (wait_end - wait_start);
double switches = switches_per_thread*num_threads;
double us_per_switch = wait_clocks/CLOCKS_PER_SEC*1000000/switches;
std::cout << "Microseconds per thread switch: " << us_per_switch;
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
for (auto s : start_times)
std::cout << 1000.0 * (s.QuadPart - l.QuadPart) / f.QuadPart <<" ms/n";
return 0;
}
Resultados de la muestra:
Milliseconds to create thread: 0.015625
Microseconds per thread switch: 0.0479687
Los primeros tiempos de inicio de hilo se ven así:
0.0632517 ms
0.117348 ms
0.143703 ms
0.18282 ms
0.209174 ms
0.232478 ms
0.263826 ms
0.315149 ms
0.324026 ms
0.331516 ms
0.3956 ms
0.408639 ms
0.4214 ms
Tenga en cuenta que si bien estos incrementos son monótonos, eso no está garantizado (aunque definitivamente hay una tendencia en esa dirección general).
Cuando escribí esto por primera vez, las unidades que usé tenían más sentido: en un 486 de 33 MHz, esos resultados no eran fracciones tan pequeñas como esta. :-) Supongo que algún día, cuando me sienta ambicioso, debería reescribir esto para usar std::async
para crear los hilos y std::chrono
para hacer el tiempo, pero ...