Problema:
Alguna parte de tu código C++ sufre de problemas de rendimiento. Estás buscando una solución ligera que te permita registrar fácilmente diferentes puntos en el tiempo e imprimir los resultados de forma adaptativa (es decir, no quieres saber que algo se ejecutó durante 1102564643 nanosegundos, solo quieres saber que tardó 1.102 segundos)
Solución
Escribí AutoBenchmark para que puedas tener la experiencia C++11 más sencilla posible para tus necesidades de micro-benchmarking.
AutoBenchmark te permite registrar diferentes puntos en el tiempo, cada uno con una etiqueta. El primer punto en el tiempo se registra cuando se construye esta instancia. AutoBenchmark soporta un número arbitrario de puntos en el tiempo.
Cuando se destruye una instancia de esta clase, automáticamente imprimirá todos los resultados del benchmark, pero solo si ha pasado una cantidad configurable de tiempo desde su construcción - esto es extremadamente útil especialmente si tienes múltiples puntos de salida en tu función que de otro modo requerirían llamar a Print() múltiples veces.
Te permite ignorar el benchmark cuando se cumple algún objetivo de rendimiento (por ejemplo, si tienes un bucle for que es lento solo para algunos puntos de datos, puedes configurar AutoBenchmark para imprimir información solo para las ejecuciones lentas).
El comportamiento por defecto (es decir, constructor con parámetros por defecto) es desactivar la impresión automática - en ese caso, puedes llamar a Print() tú mismo.
Cabecera (AutoBenchmark.hpp):
/**
* AutoBenchmark v1.1
* Written by Uli Köhler
* https://techoverflow.net
*
* https://techoverflow.net/2018/03/31/autobenchmark-automatic-multi-interval-benchmarking-in-c-using-stdchrono/
*
* Published under CC0 1.0 Universal
*/
#pragma once
#include <chrono>
#include <string>
#include <limits>
#include <vector>
using namespace std;
/**
* Benchmark automático: Permite registrar diferentes puntos en el tiempo,
* cada uno con una etiqueta. El primer punto en el tiempo se registra cuando esta instancia
* se construye. Esta clase soporta un número arbitrario de puntos en el tiempo.
*
* Cuando se destruye una instancia de esta clase, automáticamente
* imprimirá todos los resultados del benchmark, pero solo si una cantidad configurable
* de tiempo ha pasado desde su construcción, permitiéndote automáticamente
*/
class AutoBenchmark {
public:
/**
* Inicializar un benchmark que automáticamente imprime sus registros
* al destruirse si el tiempo total consumido es >= autoPrintThreshold
* en el momento de la destrucción. Solo se imprime el tiempo hasta la última etiqueta
* Record()ed.
* @param autoPrintThreshold: Cuántos segundos deben haber pasado
* para que el destructor imprima automáticamente. Por defecto es nunca imprimir.
* @param benchmarkLabel: Una etiqueta que se imprimirá una vez, antes de todos los resultados
* @param lineLabel: Una etiqueta (p. ej. sangría) que se imprimirá antes de cada línea de resultado
*/
AutoBenchmark(double autoPrintThreshold=std::numeric_limits<double>::max(), const std::string& benchmarkLabel = "", const std::string& lineLabel = " ");
~AutoBenchmark();
/**
* Registrar un punto de datos
*/
void Record(const std::string& label = "");
void Record(const char *label = "");
/**
* Imprimir todas las deltas de tiempo
*/
void Print();
/**
* Reiniciar el benchmark, como si fuera una nueva instancia.
*/
void Reset();
/**
* Retornar now() - primer punto de tiempo en segundos.
*/
double TotalSeconds();
private:
vector<std::chrono::system_clock::time_point> times;
vector<std::string> labels;
double autoPrintThreshold;
std::string benchmarkLabel;
std::string lineLabel;
};Fuente (AutoBenchmark.cpp):
#include "AutoBenchmark.hpp"
#include <iostream>
AutoBenchmark::AutoBenchmark(double autoPrintThreshold, const std::string& benchmarkLabel, const std::string& lineLabel)
: autoPrintThreshold(autoPrintThreshold), benchmarkLabel(benchmarkLabel), lineLabel(lineLabel) {
times.push_back(chrono::system_clock::now());
labels.emplace_back("Begin"); // Solo para mantener los índices iguales
}
AutoBenchmark::~AutoBenchmark() {
if(TotalSeconds() >= autoPrintThreshold) {
Print();
}
}
void AutoBenchmark::Record(const std::string &label) {
times.push_back(chrono::system_clock::now());
labels.emplace_back(label); // Solo para mantener los índices iguales
}
void AutoBenchmark::Record(const char *label) {
Record(string(label));
}
void AutoBenchmark::Print() {
if(benchmarkLabel.length()) {
cout << benchmarkLabel << '\n';
}
for (size_t i = 1; i < times.size(); i++) {
// Calcular intervalo de tiempo para comparación de tamaño
chrono::duration<double, std::nano> ns = times[i] - times[i - 1];
chrono::duration<double, std::micro> us = times[i] - times[i - 1];
chrono::duration<double, std::milli> ms = times[i] - times[i - 1];
chrono::duration<double> s = times[i] - times[i - 1];
chrono::duration<double, std::ratio<60>> min = times[i] - times[i - 1];
chrono::duration<double, std::ratio<3600>> hrs = times[i] - times[i - 1];
// Imprimir
if(ns.count() < 1000.0) {
cout << lineLabel << labels[i] << " tomó " << ns.count() << " ns\n";
} else if(us.count() < 1000.0) {
cout << lineLabel << labels[i] << " tomó " << us.count() << " μs\n";
} else if (ms.count() < 1000.0) {
cout << lineLabel << labels[i] << " tomó " << ms.count() << " ms\n";
} else if (s.count() < 60.0) {
cout << lineLabel << labels[i] << " tomó " << s.count() << " segundos\n";
} else if (min.count() < 1000.0) {
cout << lineLabel << labels[i] << " tomó " << min.count() << " minutos\n";
} else {
cout << lineLabel << labels[i] << " tomó " << hrs.count() << " horas\n";
}
}
cout << flush;
}
void AutoBenchmark::Reset() {
times.clear();
labels.clear();
times.push_back(chrono::system_clock::now());
labels.emplace_back("Begin");
}
double AutoBenchmark::TotalSeconds() {
chrono::duration s = chrono::system_clock::now() - times[0];
return s.count();
}Ejemplo de uso:
#include "AutoBenchmark.hpp"
void MySlowFunction() {
// Cada ejecución que tarda >= 0.3 segundos se imprimirá automáticamente
AutoBenchmark myBenchmark(0.3, "Resultados de ejecutar MySlowFunction():");
// .. hacer tarea 1 ...
myBenchmark.Record("Ejecutando tarea 1"); // se imprimirá como: Ejecutando tarea 1 tomó 1.2ms
// .. hacer tarea 2 ...
myBenchmark.Record("Ejecutando tarea 2");
// Ejemplo de bucle
for(size_t i = 0; ....) {
// ... hacer tarea de iteración del bucle aquí ...
myBenchmark.Record("Iteración del bucle " + std::to_string(i));
}
// myBenchmark se destruirá aquí, así que si MySlowFunction() tardó
// más de 0.3s en ejecutarse hasta que regresó, el resultado se imprimirá
// a cout automáticamente.
}Si MySlowFunction() tardó más de 0.3s en ejecutarse en total, AutoBenchmark imprimirá los resultados cuando se destruya - es decir, cuando MySlowFunction() regrese:
Resultados de ejecutar MySlowFunction():
Ejecutando tarea 1 tomó 260.826 ms
Ejecutando tarea 2 tomó 36.148 μs
Iteración del bucle 0 tomó 2.5522 segundos
Iteración del bucle 1 tomó 664.059 ms
Iteración del bucle 2 tomó 22.2772 ms
Iteración del bucle 3 tomó 57.4024 ms
Iteración del bucle 4 tomó 16.9928 ms
Iteración del bucle 5 tomó 14.0497 ms
Iteración del bucle 6 tomó 62.5218 ms