2012-07-06 16 views
11

Me gustaría saber si hay una buena manera de controlar las partes internas de mi aplicación, idealmente en la forma de una biblioteca existente.Cómo implementar estadísticas eficientes de tiempo de ejecución de C++

Mi aplicación es muy multiproceso y utiliza un sistema de mensajería para comunicarse entre hilos y para el mundo externo. Mi objetivo es supervisar qué tipo de mensajes se envían, con qué frecuencia, etc.

También podría haber otras estadísticas de una manera más general, como cuántos hilos se generan cada minuto, cuánto se llaman nuevos/eliminar , o aspectos más específicos de la aplicación; lo que sea

Lo que sería increíble es algo así como las "páginas internas" que tiene para Google Chrome, como net o chrome: // tracing, pero en una línea de comando.

Si hay una biblioteca que sea lo suficientemente genérica como para acomodar las especificidades de mi aplicación, sería genial.
De lo contrario, estoy preparado para implementar una pequeña clase que haría el trabajo, pero no sé por dónde empezar. Creo que lo más importante es que el código no debe interferir demasiado, para que las actuaciones no se vean afectadas.

¿Ustedes tienen algunos consejos sobre este asunto?

Editar: mi aplicación se ejecuta en Linux, en un entorno integrado, por desgracia, no apoyados por Valgrind :(

+0

habría gprof apoyarse? -pg en compilador gcc? – pyCthon

+0

Sí, eso es una cosa que tenemos. Aunque mi problema sería para un programa que se está ejecutando durante mucho tiempo (un servicio), entonces las estadísticas deberían estar accesibles en tiempo de ejecución :-) – Gui13

+1

Lástima que no se pudo agregar otra etiqueta, "incrustada". –

Respuesta

3

Yo recomendaría que en el código, a mantener los contadores que quedan incrementados Los contadores pueden ser static miembros de la clase. o globales. Si se utiliza una clase para definir su contador, que puede tener el constructor registrar su contador con un solo depósito junto con un nombre. Luego, se puede consultar y restablecer los contadores consultando el repositorio.

struct Counter { 
    unsigned long c_; 
    unsigned long operator++() { return ++c_; } 
    operator unsigned long() const { return c_; } 
    void reset() { unsigned long c = c_; ATOMIC_DECREMENT(c_, c); } 
    Counter (std::string name); 
}; 

struct CounterAtomic : public Counter { 
    unsigned long operator++() { return ATOMIC_INCREMENT(c_, 1); } 
    CounterAtomic (std::string name) : Counter(name) {} 
}; 

ATOMIC_INCREMENT sería un mecanismo específico de plataforma para incrementar el contador atómico. GCC proporciona un __sync_add_and_fetch incorporado para este propósito. ATOMIC_DECREMENT es similar, con GCC incorporado __sync_sub_and_fetch.

struct CounterRepository { 
    typedef std::map<std::string, Counter *> MapType; 
    mutable Mutex lock_; 
    MapType map_; 
    void add (std::string n, Counter &c) { 
     ScopedLock<Mutex> sl(lock_); 
     if (map_.find(n) != map_.end()) throw n; 
     map_[n] = &c; 
    } 
    Counter & get (std::string n) const { 
     ScopedLock<Mutex> sl(lock_); 
     MapType::const_iterator i = map_.find(n); 
     if (i == map_.end()) throw n; 
     return *(i->second); 
    } 
}; 

CounterRepository counterRepository; 

Counter::Counter (std::string name) { 
    counterRepository.add(name, *this); 
} 

Si conoce el mismo contador se incrementa en más de un hilo, a continuación, utilizar CounterAtomic. Para los contadores que son específicos de un hilo, simplemente use Counter.

+0

Este es un buen comienzo para IMO, mejor que la sugerencia de valgrind ya que violará el requisito '..so that no se verá afectado ...'. Lo que esta dirección de mocos es el aspecto de subprocesos múltiples, incrementar/restablecer contadores no necesariamente atómicos ... Pensaría en mantener algunos de estos en variables privadas de subprocesos ... – nhed

+0

@nhed: Gracias por recordarme sobre el MT de OP requisitos. Actualicé la respuesta con operaciones atómicas. – jxh

+0

Esa es una respuesta increíble. Voy a probar esa cosa y ver cómo va. Gracias también por el '__sync _ * _ y_fetch', ¡no sabía nada de eso y usé mutexes! – Gui13

0

Eche un vistazo a valgrind/callgrind.

Se puede utilizar para crear perfiles, que es lo que entiendo que está buscando. No obstante, creo que no funciona en tiempo de ejecución, pero puede generarse después de que finalice su proceso.

+0

@ Gui13: ¿Está buscando estadísticas en tiempo de compilación, como valgrind podría proporcionar? ¿O está buscando estadísticas en tiempo de ejecución, como cuántas veces el usuario hizo clic en un pop-up duck? –

+0

Tristemente, mi aplicación se ejecuta en una plataforma incrustada que no es compatible con valgrind. – Gui13

3

Supongo que está tratando de implementar la recopilación de estadísticas de tiempo de ejecución, por ejemplo, cuántos bytes envió, cuánto tiempo ha estado ejecutándose y cuántas veces el usuario ha activado una función en particular.

Normalmente, para compilar estadísticas de tiempo de ejecución como estas de una variedad de fuentes (como subprocesos de trabajo), haría que cada fuente (subproceso) incrementara sus propios contadores locales de los datos más fundamentales pero no funciona cualquier matemática larga o análisis sobre esa información aún.

Luego, de vuelta en el hilo principal (o donde quiera que se analicen estas estadísticas &), envío un mensaje de tipo RequestProgress a cada uno de los hilos del trabajador.En respuesta, los hilos de trabajo recopilarán todos los datos fundamentales y quizás realicen algunos análisis simples. Estos datos, junto con los resultados del análisis básico, se envían nuevamente a la secuencia solicitante (principal) en un mensaje ProgressReport. El hilo principal luego agrega todos estos datos, hace análisis adicionales (quizás costosos), formateando y mostrando al usuario o registrando.

El hilo principal envía este mensaje RequestProgress ya sea a petición del usuario (como cuando presionan la tecla S), o en un intervalo de tiempo. Si lo que busco es un intervalo cronometrado, normalmente implementaré otro hilo nuevo de "latido". Todo lo que hace este hilo es Sleep() durante un tiempo específico, luego envía un mensaje Heartbeat al hilo principal. El hilo principal a su vez actúa sobre este mensaje Heartbeat enviando RequestProgress mensajes a cada hilo de trabajador de donde se recopilan las estadísticas.

El acto de recopilar estadísticas parece que debería ser bastante sencillo. Entonces, ¿por qué un mecanismo tan complejo? La respuesta es doble.

En primer lugar, los subprocesos de trabajo tienen un trabajo que hacer, y las estadísticas de uso de cómputo no lo son. Tratar de refactorizar estos hilos para asumir una segunda responsabilidad ortográfica para su propósito principal es un poco como intentar meter una clavija cuadrada en un agujero redondo. No fueron creados para hacer eso, por lo que el código se resistirá a ser escrito.

En segundo lugar, el cálculo de las estadísticas de tiempo de ejecución puede ser costoso si intenta hacer demasiado, con demasiada frecuencia. Supongamos, por ejemplo, que tiene un hilo de trabajo que envía datos de multidifusión en la red y desea recopilar datos de rendimiento. Cuántos bytes, durante cuánto tiempo un período de tiempo y un promedio de cuántos bytes por segundo. Puede hacer que el hilo de trabajo calcule todo esto sobre la marcha, pero es mucho trabajo y el tiempo de CPU lo emplea mejor el hilo de trabajo haciendo lo que se supone que debe hacer: enviar datos de multidifusión. Si, en cambio, simplemente incrementó un contador de cuántos bytes ha enviado cada vez que envía un mensaje, el recuento tiene un impacto mínimo en el rendimiento del hilo. Luego, en respuesta al mensaje de vez en cuando RequestProgress se puede averiguar los tiempos de arranque y parada &, y enviar sólo que a lo largo de dejar que el hilo principal haga todo el Divison etc.

1

uso de memoria compartida (POSIX, System V, o lo que sea mmap tienes disponible). Coloque una matriz de longitud fija de enteros volátiles sin signo de 32 o 64 bits (es decir, el más grande que pueda incrementar atómicamente en su plataforma) insertando el bloque de memoria sin procesar en la definición de su matriz. Tenga en cuenta que lo volátil no le proporciona atomicidad; evita las optimizaciones del compilador que pueden destruir tus valores de estadísticas. Use intrínsecos como gcc's __sync_add_and_fetch() o los más nuevos C++ 11 atomic <> types.

Luego puede escribir un pequeño programa que se conecta al mismo bloque de memoria compartida y puede imprimir una o todas las estadísticas. Este pequeño programa de lector de estadísticas y tu programa principal tendrían que compartir un archivo de encabezado común que aplicara la posición de cada estadística en la matriz.

El inconveniente obvio aquí es que está atascado con un número fijo de contadores. Pero es difícil de superar, en cuanto a rendimiento. El impacto es el incremento atómico de un entero en varios puntos de su programa.

1

En los sistemas integrados, una técnica común es reservar un bloque de memoria para un "registro" y tratarlo como una cola circular. Escribe un código que pueda leer este bloque de memoria; lo que ayudará a tomar "instantáneas" durante el tiempo de ejecución.

Busque en la Web "registro de depuración". Debería aparecer alguna fuente que podría usar para jugar. La mayoría de las tiendas en las que he estado suelen hacer su propio uso.

En caso de tener memoria no volátil adicional, puede reservar un área y escribir en ella. Esto también incluiría archivos si su sistema es lo suficientemente grande como para admitir un sistema de archivos.

Peor caso, escriba datos en un puerto de depuración (serie).

Para medidas reales en tiempo real, solemos utilizar un osciloscopio conectado a un GPIO o punto de prueba y emitir pulsos al GPIO/punto de prueba.

0

Esa es una buena respuesta, @John Dibling! Tenía un sistema bastante similar a esto. Sin embargo, mi hilo "stat" estaba interrogando a los trabajadores 10 veces por segundo y afectó el rendimiento de los subprocesos de trabajo ya que cada vez que el subproceso "stat" solicita datos, hay una sección crítica accediendo a estos datos (contadores, etc.) y significa que el hilo de trabajo está bloqueado por el tiempo que se están recuperando estos datos. Resultó que bajo una gran carga de hilos de trabajo, esta consulta de estadísticas de 10 Hz afectó el rendimiento general de los trabajadores.

Así que cambié a un modelo de informe de estadísticas ligeramente diferente - en lugar de consultar activamente subprocesos de trabajo de los hilos principales, ahora tengo subprocesos de trabajo para informar sus contadores de estadísticas básicas a sus repositorios de estadísticas exclusivas, que pueden ser consultados por el principal hilo en cualquier momento sin impacto directo en los trabajadores.

0

Si se encuentra en C++ 11 podría utilizar std :: atómica <>

#include <atomic> 

class GlobalStatistics { 
public: 

    static GlobalStatistics &get() { 
     static GlobalStatistics instance; 
     return instance; 
    } 

    void incrTotalBytesProcessed(unsigned int incrBy) { 
     totalBytesProcessed += incrBy; 
    } 

    long long getTotalBytesProcessed() const { return totalBytesProcessed; } 


private: 

    std::atomic_llong totalBytesProcessed; 

    GlobalStatistics() { } 
    GlobalStatistics(const GlobalStatistics &) = delete; 
    void operator=(const GlobalStatistics &) = delete; 
}; 
Cuestiones relacionadas