[c++] ¿Cómo estimar la sobrecarga de cambio de contexto del hilo?



Answers

Dudo que pueda encontrar esta sobrecarga en algún lugar de la web para cualquier plataforma existente. Existen demasiadas plataformas diferentes. La sobrecarga depende de dos factores:

  • La CPU, ya que las operaciones necesarias pueden ser más fáciles o más difíciles en diferentes tipos de CPU
  • El kernel del sistema, ya que diferentes kernels tendrán que realizar diferentes operaciones en cada switch

Otros factores incluyen cómo se produce el cambio. Un cambio puede tener lugar cuando

  1. el hilo ha utilizado todo su tiempo cuántico. Cuando se inicia un hilo, puede ejecutarse durante un tiempo determinado antes de devolver el control al núcleo que decidirá quién será el siguiente.

  2. el hilo fue adelantado. Esto sucede cuando otro hilo necesita tiempo de CPU y tiene una prioridad más alta. Por ejemplo, el hilo que maneja la entrada del mouse / teclado puede ser un hilo. Independientemente del hilo que posea la CPU en este momento, cuando el usuario escribe algo o hace clic en algo, no quiere esperar hasta que el tiempo actual de los subprocesos se haya agotado por completo, quiere que el sistema reaccione de inmediato. Por lo tanto, algunos sistemas harán que el hilo actual se detenga inmediatamente y devuelva el control a otro hilo con mayor prioridad.

  3. el hilo ya no necesita más tiempo de CPU, porque está bloqueando alguna operación o simplemente se ha llamado a sleep () (o similar) para detener la ejecución.

Estos 3 escenarios pueden tener diferentes tiempos de cambio de hilo en teoría. Por ejemplo, esperaría que el último sea el más lento, ya que una llamada a suspensión () significa que la CPU se devuelve al kernel y el kernel necesita configurar una llamada de activación que asegure que el hilo se despierte después de aproximadamente la cantidad de tiempo que solicitó para dormir, luego debe quitar el hilo del proceso de programación, y una vez que el hilo se despierta, debe agregar el hilo nuevamente al proceso de programación. Todas estas pendientes tomarán una cierta cantidad de tiempo. Entonces, la llamada de espera real podría ser más larga que el tiempo que lleva cambiar a otra conversación.

Creo que si quieres saber con certeza, debes comparar. El problema es que, por lo general, tendrá que poner hilos a dormir o debe sincronizarlos utilizando mutexes. Dormir o bloquear / desbloquear mutexes tiene una sobrecarga. Esto significa que su punto de referencia incluirá estos gastos generales también. Sin tener un perfilador potente, es difícil decir cuánto tiempo de CPU se utilizó para el conmutador real y cuánto para la llamada de suspensión / exclusión mutua. Por otro lado, en un escenario de la vida real, tus hilos también dormirán o sincronizarán a través de los bloqueos. Un punto de referencia que mide puramente el tiempo de cambio de contexto es un punto de referencia sintético ya que no modela ningún escenario de la vida real. Los puntos de referencia son mucho más "realistas" si se basan en escenarios de la vida real. ¿De qué sirve una referencia de GPU que me dice que mi GPU puede manejar en teoría 2 mil millones de polígonos por segundo, si este resultado nunca se puede lograr en una aplicación 3D de la vida real? ¿No sería mucho más interesante saber cuántos polígonos puede tener una aplicación 3D real en GPU por segundo?

Desafortunadamente no sé nada de la programación de Windows. Podría escribir una aplicación para Windows en Java o quizás en C #, pero C / C ++ en Windows me hace llorar. Solo puedo ofrecerte un código fuente para POSIX.

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

uint32_t COUNTER;
pthread_mutex_t LOCK;
pthread_mutex_t START;
pthread_cond_t CONDITION;

void * threads (
    void * unused
) {
    // Wait till we may fire away
    pthread_mutex_lock(&START);
    pthread_mutex_unlock(&START);

    pthread_mutex_lock(&LOCK);
    // If I'm not the first thread, the other thread is already waiting on
    // the condition, thus Ihave to wake it up first, otherwise we'll deadlock
    if (COUNTER > 0) {
        pthread_cond_signal(&CONDITION);
    }
    for (;;) {
        COUNTER++;
        pthread_cond_wait(&CONDITION, &LOCK);
        // Always wake up the other thread before processing. The other
        // thread will not be able to do anything as long as I don't go
        // back to sleep first.
        pthread_cond_signal(&CONDITION);
    }
    pthread_mutex_unlock(&LOCK); //To unlock
}

int64_t timeInMS ()
{
    struct timeval t;

    gettimeofday(&t, NULL);
    return (
        (int64_t)t.tv_sec * 1000 +
        (int64_t)t.tv_usec / 1000
    );
}


int main (
    int argc,
    char ** argv
) {
    int64_t start;
    pthread_t t1;
    pthread_t t2;
    int64_t myTime;

    pthread_mutex_init(&LOCK, NULL);
    pthread_mutex_init(&START, NULL);   
    pthread_cond_init(&CONDITION, NULL);

    pthread_mutex_lock(&START);
    COUNTER = 0;
    pthread_create(&t1, NULL, threads, NULL);
    pthread_create(&t2, NULL, threads, NULL);
    pthread_detach(t1);
    pthread_detach(t2);
    // Get start time and fire away
    myTime = timeInMS();
    pthread_mutex_unlock(&START);
    // Wait for about a second
    sleep(1);
    // Stop both threads
    pthread_mutex_lock(&LOCK);
    // Find out how much time has really passed. sleep won't guarantee me that
    // I sleep exactly one second, I might sleep longer since even after being
    // woken up, it can take some time before I gain back CPU time. Further
    // some more time might have passed before I obtained the lock!
    myTime = timeInMS() - myTime;
    // Correct the number of thread switches accordingly
    COUNTER = (uint32_t)(((uint64_t)COUNTER * 1000) / myTime);
    printf("Number of thread switches in about one second was %u\n", COUNTER);
    return 0;
}

Salida

Number of thread switches in about one second was 108406

Más de 100,000 no es tan malo y eso a pesar de que tenemos esperas bloqueadas y condicionales. Supongo que sin todas estas cosas al menos el doble de conmutadores de subprocesos serían posibles por segundo.

Question

Estoy tratando de mejorar el rendimiento de la aplicación de subprocesos con fechas límite en tiempo real. Se ejecuta en Windows Mobile y está escrito en C / C ++. Tengo la sospecha de que la alta frecuencia de la conmutación de hilos puede estar causando una sobrecarga tangible, pero no puedo probarlo ni desmentirlo. Como todos saben, la falta de pruebas no es una prueba de lo contrario :).

Por lo tanto, mi pregunta es doble:

  • Si existe, ¿dónde puedo encontrar alguna medida real del costo de cambiar el contexto del hilo?

  • Sin perder tiempo escribiendo una aplicación de prueba, ¿cuáles son las formas de estimar la sobrecarga de conmutación de subprocesos en la aplicación existente?

  • ¿Alguien sabe una forma de averiguar el número de interruptores de contexto (encendido / apagado) para un hilo dado?




No lo sé, pero ¿tiene los contadores de rendimiento habituales en Windows Mobile? Podrías mirar cosas como cambios de contexto / seg. No sé si hay alguno que mida específicamente el tiempo de cambio de contexto.







Mis 50 líneas de C ++ muestran para Linux (QuadCore Q6600) el tiempo de cambio de contexto ~ 0.9us (0.75us para 2 hilos, 0.95 para 50 hilos). En este punto de referencia, los hilos invocan el rendimiento inmediatamente cuando obtienen una cantidad de tiempo.




Links