¿Cómo estimar la sobrecarga de conmutación de contexto de hilo?


Estoy tratando de mejorar el rendimiento de la aplicación enhebrada con plazos en tiempo real. Se ejecuta en Windows Mobile y está escrito en C / C++. Tengo la sospecha de que la alta frecuencia de cambio de hilo podría estar causando gastos generales tangibles, pero no puede probarlo o refutarlo. Como todo el mundo sabe, la falta de pruebas no es una prueba de lo contrario :).

Así que mi pregunta es doble:

  • Si existe en absoluto, ¿dónde puedo encontrar las mediciones reales del costo de cambiar el contexto del hilo?

  • Sin gastar 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 manera de averiguar el número de cambios de contexto (on / off) para un hilo determinado?

Author: Ignas Limanauskas, 2008-11-20

8 answers

Mientras dijiste que no querías escribir una aplicación de prueba, hice esto para una prueba anterior en una plataforma Linux ARM9 para averiguar cuál es la sobrecarga. Eran solo dos hilos que aumentarían:: thread:: yield() (o, ya sabes) e incrementarían alguna variable, y después de un minuto más o menos (sin otros procesos en ejecución, al menos ninguno que haga algo), la aplicación imprimía cuántos cambios de contexto podía hacer por segundo. Por supuesto, esto no es realmente exacto, pero el punto es que ambos hilos rindieron la CPU entre sí, y era tan rápido que ya no tenía sentido pensar en la sobrecarga. Por lo tanto, simplemente siga adelante y simplemente escriba una prueba simple en lugar de pensar demasiado en un problema que puede ser inexistente.

Aparte de eso, puede intentar como 1800 sugerido con contadores de rendimiento.

Ah, y recuerdo una aplicación que se ejecuta en Windows CE 4.X, donde también tenemos cuatro hilos con conmutación intensiva a veces, y nunca tuvimos problemas de rendimiento. También intentamos implementar el subproceso central sin subprocesos en absoluto, y no vimos ninguna mejora en el rendimiento (la interfaz gráfica de usuario solo respondió mucho más lento, pero todo lo demás era el mismo). Tal vez puedas intentar lo mismo, ya sea reduciendo el número de cambios de contexto o eliminando hilos por completo (solo para probar).

 14
Author: OregonGhost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-20 09:37:23

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 lleva a cabo el cambio. Un interruptor puede tener lugar cuando

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

  2. El hilo se adelantó. Esto sucede cuando otro subproceso necesita tiempo de CPU y tiene una prioridad más alta. Por ejemplo, el hilo que maneja la entrada del ratón / teclado puede ser un hilo de este tipo. No importa qué hilo posee la CPU en este momento, cuando el usuario escribe algo o hace clic en algo, no quiere esperar hasta que el tiempo de los hilos actuales se haya agotado por completo, quiere ver el sistema reaccionando inmediatamente. 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 subproceso ya no necesita tiempo de CPU, porque está bloqueando alguna operación o simplemente se llama sleep() (o similar) para dejar de ejecutarse.

Estos 3 escenarios podrían tener diferentes tiempos de conmutación de hilo en teoría. Por ejemplo, esperaría que el último sea más lento, ya que una llamada a sleep () significa que la CPU se devuelve al kernel y el kernel necesita configurar una llamada de activación que se asegure de que el subproceso se despierte después de aproximadamente la cantidad de tiempo que solicitó para dormir, entonces debe sacar el subproceso del proceso de programación, y una vez que el subproceso se despierte, debe agregar el subproceso nuevamente al proceso de programación. Todas estas pendientes tardarán un poco de tiempo. Tan la llamada de sueño real puede ser más larga que el tiempo que toma cambiar a otro hilo.

Creo que si quieres saber con seguridad, debes comparar. El problema es que por lo general tendrá que poner los hilos en reposo o debe sincronizarlos usando mutexes. Dormir o Bloquear/Desbloquear mutexes tiene en sí mismo una sobrecarga. Esto significa que su benchmark también incluirá estos gastos generales. Sin tener un potente generador de perfiles, es difícil decir más adelante cuánto tiempo de CPU se utilizó para el interruptor real y cuánto por el sleep / mutex-call. Por otro lado, en un escenario de la vida real, sus hilos dormirán o se sincronizarán a través de bloqueos también. 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é uso es un punto de referencia GPU que me dice que mi GPU en teoría puede manejar 2 mil millones de polígonos por segundo, si este resultado nunca puede ser logrado en una aplicación 3D de la vida real? ¿No sería mucho más interesante saber cuántos polígonos una aplicación 3D de la vida real puede tener la GPU manejar un segundo?

Desafortunadamente no sé nada de programación de Windows. Podría escribir una aplicación para Windows en Java o tal vez 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 que a pesar de que tenemos bloqueo y espera condicional. Supongo que sin todas estas cosas, al menos el doble de interruptores de hilo eran posibles por segundo.

 25
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-15 16:58:17

No puedes estimarlo. Tienes que medirlo. Y va a variar dependiendo del procesador en el dispositivo.

Hay dos formas bastante simples de medir un cambio de contexto. Uno involucra código, el otro no.

Primero, la forma del código (pseudocódigo):

DWORD tick;

main()
{
  HANDLE hThread = CreateThread(..., ThreadProc, CREATE_SUSPENDED, ...);
  tick = QueryPerformanceCounter();
  CeSetThreadPriority(hThread, 10); // real high
  ResumeThread(hThread);
  Sleep(10);
}

ThreadProc()
{
  tick = QueryPerformanceCounter() - tick;
  RETAILMSG(TRUE, (_T("ET: %i\r\n"), tick));
}

Obviamente hacerlo en un bucle y promediar será mejor. Tenga en cuenta que esto no solo mide el cambio de contexto. También estás midiendo la llamada a ResumeThread y no hay garantía de que programador va a cambiar inmediatamente a su otro hilo (aunque la prioridad de 10 debería ayudar a aumentar las probabilidades de que lo hará).

Puede obtener una medición más precisa con CeLog conectándose a los eventos del planificador, pero está lejos de ser fácil de hacer y no está muy bien documentado. Si realmente quieres ir por esa ruta, Sue Loh tiene varios blogs en él que un motor de búsqueda puede encontrar.

La ruta sin código sería usar el Rastreador Remoto del Kernel. Instalar eVC 4.0 o la versión eval de Constructor de plataforma para conseguirlo. Le dará una visualización gráfica de todo lo que el núcleo está haciendo y puede medir directamente un interruptor de contexto de hilo con las capacidades de cursor proporcionadas. De nuevo, estoy seguro de que Sue tiene una entrada de blog sobre el uso de Kernel Tracker también.

Dicho todo esto, vas a encontrar que los interruptores de contexto de hilo intra-proceso CE son muy, muy rápidos. Son los switches de proceso los que son caros, ya que requiere intercambiar el proceso activo en RAM y luego hacer el migración.

 14
Author: ctacke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-20 14:07:08

Mis 50 líneas de C++ muestran para Linux (QuadCore Q6600) el tiempo de cambio de contexto ~ 0.9 us (0.75 us para 2 hilos, 0.95 para 50 hilos). En este benchmark hilos de llamada rendimiento inmediatamente cuando obtienen un quantum de tiempo.

 7
Author: bobah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-13 19:35:39

Solo he intentado estimar esto una vez y eso fue en un 486! El resultado fue que el cambio de contexto del procesador estaba tomando alrededor de 70 instrucciones para completar (tenga en cuenta que esto estaba sucediendo para muchas llamadas a la api del sistema operativo, así como el cambio de hilo). Calculamos que estaba tomando aproximadamente 30us por interruptor de rosca (incluida la sobrecarga del sistema operativo) en un DX3. Los pocos miles de cambios de contexto que estábamos haciendo por segundo absorbían entre el 5-10% del tiempo del procesador.

Cómo se traduciría eso a un multi-core, multi-ghz procesador moderno No lo sé, pero me imagino que a menos que usted estaba completamente ir sobre la parte superior con el hilo de cambiar su una sobrecarga insignificante.

Tenga en cuenta que la creación/eliminación de subprocesos es un hogger CPU/OS más caro que activar/desactivar subprocesos. Una buena política para aplicaciones con muchos subprocesos es usar grupos de subprocesos y activar / desactivar según sea necesario.

 5
Author: Tim Ring,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-20 12:16:50

El cambio de contexto es caro, como regla general cuesta 30µs de sobrecarga de CPU http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html

 5
Author: Soroush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-19 08:21:06

El problema con los cambios de contexto es que tienen un tiempo fijo. GPU implementado 1 ciclo de cambio de contexto entre subprocesos. Lo siguiente, por ejemplo, no se puede enhebrar en CPU:

double * a; 
...
for (i = 0; i < 1000; i ++)
{
    a[i] = a[i] + a[i]
}

Porque su tiempo de ejecución es mucho menor que el costo del cambio de contexto. En Core i7 este código toma alrededor de 1 micro segundo (depende del compilador). Así que el tiempo de cambio de contexto importa porque define cómo se pueden enhebrar trabajos pequeños. Supongo que esto también proporciona un método para una medición efectiva de cambio de contexto. Compruebe cuánto tiempo tiene que ser la matriz (en el ejemplo superior) para que dos subprocesos del grupo de subprocesos comiencen a mostrar alguna ventaja real en comparación con un solo subproceso. Esto puede convertirse fácilmente en 100 000 elementos y, por lo tanto, el tiempo efectivo de cambio de contexto estaría en algún lugar del rango de 20us dentro de la misma aplicación.

Todas las encapsulaciones utilizadas por el grupo de subprocesos tienen que contarse para el tiempo de cambio de subproceso porque eso es lo que todo se reduce a (en el final).

Atmapuri

 3
Author: Atmapuri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-30 00:14:11

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

 1
Author: 1800 INFORMATION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-20 09:27:04