2012-03-02 7 views
6

estoy escribiendo una clase de barra de progreso que da salida a una barra de progreso actualizado cada n garrapatas a un std::ostream:tiempo Estimación de la izquierda en C++ 11

class progress_bar 
{ 
public: 
    progress_bar(uint64_t ticks) 
    : _total_ticks(ticks), ticks_occured(0), 
     _begin(std::chrono::steady_clock::now()) 
    ... 
    void tick() 
    { 
    // test to see if enough progress has elapsed 
    // to warrant updating the progress bar 
    // that way we aren't wasting resources printing 
    // something that hasn't changed 
    if (/* should we update */) 
    { 
     ... 
    } 
    } 
private: 
    std::uint64_t _total_ticks; 
    std::uint64_t _ticks_occurred; 
    std::chrono::steady_clock::time_point _begin; 
    ... 
} 

Me gustaría también de salida el tiempo restante. He encontrado una fórmula en another question que indica el tiempo restante está (nombres de variables cambian para adaptarse a mi clase):

time_left = (time_taken/_total_ticks) * (_total_ticks - _ticks_occured)

Las piezas que me gustaría reemplazar a mi clase son el time_left y la time_taken, usando C++ 11 nuevo encabezado <chrono>.

Sé que necesito usar un std::chrono::steady_clock, pero no estoy seguro de cómo integrarlo en el código. Supongo que la mejor manera de medir el tiempo sería un std::uint64_t en nanosegundos.

Mis preguntas son:

  1. ¿Hay una función en <chrono> que convertirá los nanosegundos en un std::string, decir algo como "3m12s"?
  2. ¿Debo usar el std::chrono::steady_clock::now() cada vez que actualizo mi barra de progreso, y restarlo de _begin para determinar time_left?
  3. ¿Hay un mejor algoritmo para determinar time_left

Respuesta

7

¿Hay una función en la que convertirá los nanosegundos en un std :: string, decir algo como "3m12s"?

No.Pero te mostraré cómo puedes hacer esto fácilmente a continuación.

¿Debo usar el std :: crono :: :: steady_clock ahora() cada vez que actualizo mi barra de progreso, y restar que a partir de _begin para determinar TIME_LEFT?

Sí.

¿Hay un mejor algoritmo para determinar TIME_LEFT

Sí. Vea abajo.

Editar

había mal interpretado originalmente "ticks" como "reloj de garrapatas", cuando en realidad "garrapatas" tiene unidades de trabajo y _ticks_occurred/_total_ticks puede interpretarse como% JOB_DONE. Así que he cambiado la propuesta progress_bar a continuación según corresponda.

creo que la ecuación:

time_left = (time_taken/_total_ticks) * (_total_ticks - _ticks_occured) 

es incorrecto. No pasa un control de cordura: si _ticks_occured == 1 y _total_ticks es grande, entonces time_left es aproximadamente igual (vale, un poco menos) time_taken. Eso no tiene sentido.

estoy reescribiendo la ecuación anterior sea:

time_left = time_taken * (1/percent_done - 1) 

donde

percent_done = _ticks_occurred/_total_ticks 

Ahora, como percent_done se aproxima a cero, time_left tiende a infinito, y cuando percent_done se aproxima a 1, 'time_left se aproxima a 0. Cuando percent_done es 10%, time_left es 9*time_taken. Esto cumple mis expectativas, asumiendo un costo de tiempo aproximadamente lineal por turno de trabajo.

class progress_bar 
{ 
public: 
    progress_bar(uint64_t ticks) 
    : _total_ticks(ticks), _ticks_occurred(0), 
     _begin(std::chrono::steady_clock::now()) 
// ... 
    {} 
    void tick() 
    { 
    using namespace std::chrono; 
    // test to see if enough progress has elapsed 
    // to warrant updating the progress bar 
    // that way we aren't wasting resources printing 
    // something that hasn't changed 
    if (/* should we update */) 
    { 
     // somehow _ticks_occurred is updated here and is not zero 
     duration time_taken = Clock::now() - _begin; 
     float percent_done = (float)_ticks_occurred/_total_ticks; 
     duration time_left = time_taken * static_cast<rep>(1/percent_done - 1); 
     minutes minutes_left = duration_cast<minutes>(time_left); 
     seconds seconds_left = duration_cast<seconds>(time_left - minutes_left); 
    } 
    } 
private: 
    typedef std::chrono::steady_clock Clock; 
    typedef Clock::time_point time_point; 
    typedef Clock::duration duration; 
    typedef Clock::rep rep; 
    std::uint64_t _total_ticks; 
    std::uint64_t _ticks_occurred; 
    time_point _begin; 
    //... 
}; 

Tráfico en std :: chrono :: duraciones siempre que pueda. De esa manera, <chrono> realiza todas las conversiones por usted. typedefs puede facilitar la escritura con los nombres largos. Y dividir el tiempo en minutos y segundos es tan fácil como se muestra arriba.

Como bames53 notas en su respuesta, si desea utilizar mi instalación <chrono_io>, es genial también. Sus necesidades pueden ser lo suficientemente simples como para no querer. Es una llamada de juicio. La respuesta de bames53 es una buena respuesta. Pensé que estos detalles adicionales podrían ser útiles también.

Editar

accidentalmente me dejó un error en el código de seguridad. Y en lugar de simplemente parchear el código anterior, pensé que sería una buena idea señalar el error y mostrar cómo usar <chrono> para solucionarlo.

El error está aquí:

duration time_left = time_taken * static_cast<rep>(1/percent_done - 1); 

y aquí:

typedef Clock::duration duration; 

En la práctica steady_clock::duration se basa por lo general en un tipo entero. <chrono> llama esto al rep (abreviatura de representación). Y cuando percent_done es mayor que 50%, el factor multiplicado por time_taken va a ser menor que 1. Y cuando rep es integral, eso se convierte en 0. Entonces este progress_bar solo se comporta bien durante el primer 50% y predice 0 izquierda durante el último 50%.

La clave para solucionar esto es traficar en duration s que se basan en coma flotante en lugar de enteros. Y <chrono> hace que esto sea muy fácil de hacer.

typedef std::chrono::steady_clock Clock; 
typedef Clock::time_point time_point; 
typedef Clock::period period; 
typedef std::chrono::duration<float, period> duration; 

duration tiene ahora el mismo período garrapata tan steady_clock::duration pero utiliza un float para la representación. Y ahora el cálculo para time_left puede dejar fuera de la static_cast:

duration time_left = time_taken * (1/percent_done - 1); 

Aquí está todo el paquete de nuevo con estas soluciones:

class progress_bar 
{ 
public: 
    progress_bar(uint64_t ticks) 
    : _total_ticks(ticks), _ticks_occurred(0), 
     _begin(std::chrono::steady_clock::now()) 
// ... 
    {} 
    void tick() 
    { 
    using namespace std::chrono; 
    // test to see if enough progress has elapsed 
    // to warrant updating the progress bar 
    // that way we aren't wasting resources printing 
    // something that hasn't changed 
    if (/* should we update */) 
    { 
     // somehow _ticks_occurred is updated here and is not zero 
     duration time_taken = Clock::now() - _begin; 
     float percent_done = (float)_ticks_occurred/_total_ticks; 
     duration time_left = time_taken * (1/percent_done - 1); 
     minutes minutes_left = duration_cast<minutes>(time_left); 
     seconds seconds_left = duration_cast<seconds>(time_left - minutes_left); 
     std::cout << minutes_left.count() << "m " << seconds_left.count() << "s\n"; 
    } 
    } 
private: 
    typedef std::chrono::steady_clock Clock; 
    typedef Clock::time_point time_point; 
    typedef Clock::period period; 
    typedef std::chrono::duration<float, period> duration; 
    std::uint64_t _total_ticks; 
    std::uint64_t _ticks_occurred; 
    time_point _begin; 
    //... 
}; 

Nada como un poco de pruebas ... ;-)

+0

El motivo del "tic" es representar el progreso, es decir: tengo 1,000,000 de operaciones (ticks) para completar. Por lo que puedo decir, boost tiene un 'chrono_io'. ¿Recomendarías usar eso? – nerozehl

+1

Oh, "tic" representa una unidad de trabajo, no una unidad de tiempo? Si es así, no entiendo bien tu pregunta. Leí por error "tic del reloj". El impulso '' es una copia autorizada de mi ''. Debería estar bien usarlo si lo encuentra útil. Estoy proponiendo '' para un estándar futuro, por lo que sería bueno tener comentarios al respecto. –

+0

Sí, tick siendo una unidad de trabajo ... Supuse que debería haber sido más claro, lo siento por los problemas en eso. ¡Muchas gracias! :) – nerozehl

3

La biblioteca incluye crono tipos de representación de duraciones. No debe convertir eso en un entero plano de alguna unidad 'conocida'. Cuando desee una unidad conocida, simplemente use los tipos de crono, p. 'std :: chrono :: nanoseconds' y duration_cast. O cree su propio tipo de duración usando una representación de punto flotante y una de las relaciones SI. P.ej. std::chrono::duration<double,std::nano>. Sin duration_cast o un redondeo de duración de punto flotante está prohibido en tiempo de compilación.

Las instalaciones de IO para chrono no llegaron a C++ 11, pero puede obtener la fuente de here. Al usar esto, puede ignorar el tipo de duración e imprimirá las unidades correctas. No creo que haya nada allí que muestre el tiempo en minutos, segundos, etc., pero tal cosa no debería ser demasiado difícil de escribir.

No sé si hay demasiadas razones para preocuparse por llamar al steady_clock::now() con frecuencia, si eso es lo que está pidiendo. Esperaría que la mayoría de las plataformas tengan un temporizador bastante rápido para ese tipo de cosas. Sin embargo, depende de la implementación. Obviamente, está causando un problema para usted, así que tal vez solo pueda llamar al steady_clock::now() dentro del bloque if (/* should we update */), lo que debería poner un límite razonable en la frecuencia de la llamada.

Obviamente, hay otras maneras de estimar el tiempo restante. Por ejemplo, en lugar de tomar el promedio sobre el progreso hasta el momento (que es lo que hace la fórmula que muestra), podría tomar el promedio de los últimos N tics. O haga ambas cosas y tome un promedio ponderado de las dos estimaciones.

+0

Would ¿Recomiendas usar boost :: chrono over std :: chrono? – nerozehl

+0

boost :: chrono tiene las facilidades adicionales de IO, y su resolución en Windows es mejor que las bibliotecas en VS11 beta. Pero al mismo tiempo me gusta usar el estándar siempre que sea posible. No veo un problema de ninguna manera. – bames53