Optimización 90-10

Ya dicen que el 90% del tiempo de ejecución está en el 10% del código, y en situaciones como la que menciono hoy es muy patente.

En un código para leer un determinado tipo de fichero tenía la siguiente secuencia (la función completa es unas 7 veces más larga):

for (ii = 0; ii < 3; ++ii) {
 if (tokens[ii].empty()) {
 break;
 }
 elem.clear();
 boost::split(elem, tokens[ii], boost::is_any_of("/"));

 if (elem.empty()) {
 break;
 }
 a[ii] = stoi(elem[0]) - 1;
}

Este fragmento se repite unas 80000 veces en el fichero de pruebas (los ficheros más grandes pueden requerir más de 2 millones de iteraciones). La función contenedora ya estaba optimizada en términos de uso de memoria, creación de objetos temporales, rutas más probables, etc.

Aunque en la versión de despliegue la función es suficientemente rápida (0,45 segundos de media), en modo depuración se vuelve insoportablemente lenta, en torno a los 60 segundos.

Analizando el problema se ve que lo único importante es el primer elemento del vector, es decir, lo que va desde el comienzo de tokens[ii] hasta el primer carácter “/”, por lo que la función puede re-escribirse como sigue:

for (ii = 0; ii < 3; ++ii) {
 if (tokens[ii].empty()) {
 break;
 }

 int p = tokens[ii].find('/');
 a[ii] = stoi(tokens[ii].substr(0, p)) - 1;
}

En modo depuración ahora tarda 28 segundos: ¡50% menos!. En modo final la optimización es menos notable para ficheros pequeños, ya que la función de Boost en cuestión es mucho más eficiente en este modo. De todas formas, los tiempos han mejorado y ahora tardo menos en abrir mis ficheros para corregir errores.

Daemon-start-stop

Hoy estuve creando un servicio en Debian para iniciar automáticamente un pequeño servidor (el demoniodaemon en inglés) en la empresa. El proceso en sí es bastante sencillo, basta con copiar el fichero /etc/init.d/skeleton a /etc/init.d/<nuestro_daemon>, darle permisos de ejecución y modificar los valores correspondientes a nuestro demonio.

Detallo los puntos de “ensayo y error” en los que trabajé y que me parecen más interesantes. Al final del post dejo, para los curiosos, el servicio de arranque finalizado (he cambiado el nombre por motivos internos).

  • Si se pasan argumentos al ejecutable, lo más sencillo es invocar al mismo con la ruta absoluta.
  • Los argumentos deben ser lo último en la línea de comandos del daemon-start-stop, y no justo después del –exec (como pensaría uno de primeras). Ejemplo: daemon-start-stop –start –quier –exec $DAEMON_EXEC –test — $DAEMON_ARGS
  • Muchas veces queremos ejecutar el demonio como un usuario diferente (permisos, seguridad). En ese caso, se usa el parámatro –chuid [<user>][:<group>], siendo opcionales cualquier de ellos (y siempre los argumentos de ejecución del demonio al final, recordad esto).
  • El parámetro –background ejecuta el programa en segundo plano y no tenemos que preocuparnos nosotros de hacer el fork de nuestro proceso. Importante cuando el demonio se lanza durante el arranque, para no frenarlo.
  • Si nuestro programa no crear su pidfile podemos pedirle al sistema que lo haga por nosotros con la opción –make-pidfile. El pidfile es un fichero especial del tipo /var/run/<nombreproceso>.pid que únicamente tiene dentro el identificador de proceso, y que se utiliza para localizar procesos de forma rápida.

Más información sobre el daemon-start-stop.

Continue reading

Qt + resources + fonts + stylesheets

A partir de esta entrega cambiaré el título de las anec-notas para reflejar mejor la temática y ahorrar espacio en la cabecera de la entrada: muchas veces estará en inglés (por eso de ser el idioma universal de la programación), quitaré el “anec-notas” del mismo y dejaré la indicación únicamente en la categoría del post.

Terminado este preámbulo, esta entrega mostrará una forma de incluir fuentes propias en un proyecto Qt, al menos la que más me gusta. Lo primero será ubicar el fichero de fuentes. Personalmente recomiendo utilizar un fichero de recursos (QRC) en el cual embeber la fuente:

<RCC>
 <qresource>
 <file>fuentes/mipropiafuente.ttf</file>
 </qresource>
</RCC>

Esta fuente deberá estar ubicada en “./fuentes/” respecto al fichero de recursos.

Antes de poder utilizar una fuente, hay que añadirla a la base de datos de fuentes de la aplicación. Para ello se usa la siguiente llamada:

QFontDatabase::addApplicationFont(":/fuentes/mipropiafuente.ttf");

A partir de este momento la fuente estará disponible en toda la aplicación. Bastará con especificarla en una hoja de estilos:

QWidget
{
 font-family: "Nombre real de nuestra fuente";
 font-size: 14px;
 color: #999999;
}

O directamente desde código:

// "pt" es un "QPainter *", seguramente un argumento del evento paint(...)

QFont fold(pt->font());
QFont fnew(fold);

fnew.setFamily("Nombre real de nuestra fuente");
fnew.setWeight(QFont::Bold);
pt->setFont(fnew);

// Escribir texto

pt->setFont(fold); // restablecer la fuente

Sugerencias finales sobre los stylesheets

Para agilizar la personalización de la interfaz sugiero cargar el fichero de estilos por fuera del fichero de recursos durante la etapa de desarrollo, y únicamente cargarlo desde el QRC cuando se pase a producción.

Además, si se están cambiando ficheros ya incluidos en el QRC, tales como imágenes, habrá que saber que los recursos de Qt sólo se re-compilan cuando el fichero .qrc es modificado, no cuando los ficheros incluidos lo hacen. Una buena técnica para automatizar este proceso es “tocar” el fichero QRC antes de cada compilación, al menos durante las pruebas de diseño de interfaz (la compilación de un QRC puede tomar su tiempo si los recursos incluidos son grandes). Para ello sugiero ver la entrada anterior sobre el comando touch en VisualStudio.

Anec-notas (V)

En estas últimas semanas he estado desarrollando una aplicación para captura de imágenes usando cámaras PointGrey. Lo primero que he de comentar es que su SDK está verdaderamente mal documentado, y salirse de los ejemplos que se instalan junto a él supone un sin fin de dolores de cabeza, así que no os extrañéis si de vez en cuando aparecen anec-notas sobre estas cámaras. En esta ocasión comentaré sólo un par.

Bayer

Para los que no sepan lo que es, os dejo la Wikipedia. Como resumen, muchas cámaras RGB no son tales, sino cámaras en escala de grises donde cada píxel captura una componente de color. Es decir, hay píxeles rojos, píxeles verdes y píxeles azules, dependiendo de la configuración del filtro. Como consecuencia, la imagen obtenida sigue siendo de un único canal, y hay que aplicarle un filtro para convertirlo a tres canales (RGB) y recuperar la información faltante (el píxel verde no captura información para el canal rojo, por ejemplo).

El SDK de las cámaras PointGrey tiene su propio conversor (obviaré los tipos de datos por simplicidad).

m_Camera-&gt;RetrieveBuffer(&amp;m_Frame);
m_Frame.Convert(PIXEL_FORMAT_RGB8, &amp;m_FrameAux);

Este filtro actúa usando una interpolación al más cercano, por lo que, como el lector podrá deducir, una cámara de 640×480 realmente capturaría como una cámara de 240×320, con un área de píxel (en el sensor) cuatro veces mayor. El resultado final es una imagen de muy poca calidad.

En cambio, es posible utilizar la función cvtColor de OpenCV para realizar una conversión de mejor calidad:

cv::Mat cvframe(m_Frame.GetRows(), m_Frame.GetCols(), CV_8UC1, m_Frame.GetData());
cvtColor(cvframe, cvframe, CV_BayerBG2RGB);

Además, este cambio de API conlleva una sobrecarga apenas notable tanto en consumo de memoria como en tiempo, ya que la matriz de OpenCV se crea utilizando la misma área de memoria del frame creado por el SDK de PointGrey. Obviamente si hay un overhead debido al uso de una interpolación más pesada.

Como comentario final, el tipo de conversión Bayer ha utilizar no es homogéneo entre las cámaras PointGrey, así la FireFly MV usa el indicado arriba, mientras que una BlackFly requeriría un CV_BayerBG2BGR.

Instancias

Otro detalle que  me ha dado más de un dolor de cabeza ha sido el hecho de que la aplicación que desarrollo actualmente debe mostrar en varios puntos de la ejecución la imagen en vivo de las diversas cámaras conectadas. El problema radica en que si un objeto de captura de PointGrey (la cámara) no es destruido, y luego se crea otro conectado al mismo dispositivo, errores inesperados pueden aparecer al tratar de escribir los registros, o, el que me ocurría a mí, tratar de cambiar el framerate. Ni molestarme en comentar la de tiempo que pasé buscando cualquier leak de objetos de captura por todo el código.

Anec-notas (IV)

Parece contradictorio que durante las vacaciones esté publicando más posts sobre mi trabajo que durante los meses laborales, pero qué le vamos a hacer, la vida es así.

Esta entrada la quiero dedicar al Visual Studio, así que allá vamos.

Implementar el comando “touch”

Unos de esos comando de UNIX que siempre me han gustado es el touch (para los que no lo conozcan, este comando modifica la cabecera de un fichero actualizando su fecha de modificación, creando el archivo si éste no existiere). Windows no tiene un comando equivalente a primera mano. Buscando un poco me encontré que se puede emular usando

copy /b <fichero> +,,

Donde es el nombre del fichero a “tocar” (pueden usarse comodines). Una limitación de este comando es que el fichero debe estar en el mismo directorio que el activo desde donde se ejecuta el comando. Ejecutar el comando siguiente no funcionará correctamente sino que copiará el fichero al directorio actual.

copy /b ..\config\game.ini +,,

La línea correcta en ese caso sería

copy /b ..\config\game.ini +,, ..\config\game.ini

Ahora bien, ¿a qué todo esto en Visual Studio? Imaginemos el siguiente escenario: estamos utilizando Qt y tenemos una hoja de estilos definida en un fichero QSS (Qt Stylesheet), el cual está incluido como recurso en un fichero QRC. Este fichero se recompila únicamente cuando se ha modificado, no cuando se han modificado los ficheros que incluye. Por lo que si estamos modificando continuamente el fichero de estilos tendríamos que “modificar” el fichero QRC para que se recompilase. En este caso el comando touch es un gran aliado. Para automatizar esta tarea podemos recurrir a los “Build Events”, más específicamente al “Pre-Build Event”. Está ubicado en “Project Properties > Build Events > Pre-Build Event”, donde especificamos el comando a utilizar (el ampersand “&” es un separador de comandos para poder indicar varios comandos uno a continuación de otro):

cd Resources &amp; copy /b *.qrc +,,

Los “Build Events” sólo permiten ejecutar un único comando (línea), por lo que si queremos realizar varias tareas lo mejor será definir un fichero por lotes que las ejecute. Otro detalle a tener en cuenta es que los ficheros QRC pueden tardar cierto tiempo en compilarse si contienen muchos recursos, especialmente imágenes, por lo que es conveniente que este evento sólo esté presente en una configuración de proyecto, tal como la Debug, o crearnos una especial.

Detalles de usabilidad

Acá me gustaría criticar comentar un detalle en la pantalla de creación de nuevos proyectos.

Nuevo proyecto VS

Resulta que en los campos de “Nombre” y “Nombre de la solución”, este cuadro de diálogo comprueba si el texto introducido es ““, en cuyo caso desactiva el botón “OK”. Probad vosotros y borrad una letra para que veáis cómo podéis crear el proyecto. Re-escribid esa letra y el botón “OK” se desactivará. ¿Tantos avances que se han hecho en usabilidad y no podían usar la propiedad “placeholder” en este caso?

Anec-notas (III)

Las vacaciones no siempre son vacaciones al 100%, cuando se trabaja en el mundo informático lo normal es pasear por todo el país con el portátil a cuestas y tener que realizar cambios sobre la marcha o pequeños ajustes a algún proyecto. Y dado que ya estaba en ésas he acá la tercera entrega de anéc-notas.

QImage

Entre los ajustes que he tenido que hacer hay uno que se refiere a la adquisición de imágenes a 60fps desde diferentes modelos de sensores. El flujo de datos involucra la visualización de dichos fotogramas en una interfaz programada en Qt. Sin entrar en más detalles, la imagen la adquiría en un QImage para su representación. Luego, con el fin de procesarla con otros algoritmos intermedios, obtenía el puntero al bloque de datos mediante un QImage::bits(). De ahí surgen los dos comentarios de esta sección.

El primero es que dicho arreglo de bytes puede contener un padding final, por lo que los píxeles de una fila no están a continuación de la anterior, sino después de un pequeño espacio. Esto no ocurre si la imagen ha sido creada desde un puntero (lo cual evita hacer una copia del bloque de memoria), pero si la imagen se manipula o se crea de otra forma hay que tener en cuenta este espacio.

El otro detalle me pareció muy curioso. Este método tiene dos variantes, una “const” y otra “no-const”. La diferencia principal es que la variante “no-const” crea una copia antes de devolver el puntero, por lo que cualquier optimización que uno crea que está haciendo al acceder directamente al bloque de memoria puede irse al pozo si no tenemos cuidado, por ejemplo, si hacemos algo como:

swap_rgb_to_gbr(img.bits());

Y la función es algo de este estilo:

void swap_rgb_to_gbr(unsigned char *pixels);

La mejor solución que he podido encontrar implica forzar un casting:

const unsigned char *buffer = img.bits();
swap_rgb_to_gbr(const_cast&lt; unsigned char* &gt;(buffer));

Ahora bien, cuando se trata de cambiar el tamaño de una imagen, QImage es poco más que un niño de 2 años con unos lápices de colores en la mano. Lo mejor es utilizar opciones más potentes, como por ejemplo OpenCV (asumiremos que src es ya una imagen de OpenCV válida):

cv::Mat dst;
cv::resize(src, dst, cv::Size(nuevo_ancho, nuevo_alto), 0.0, 0.0, cv::INTER_LANCZOS4);

QImage resizedimg(nuevo_ancho, nuevo_alto, QImage::Format_RGB888); // 24bits por píxel

const unsigned char *dstdata = dst.data;
const int rowsize = nuevo_ancho * 3; // RGB
for (int ii = 0; ii &lt; nuevo_anchoe; ++ii, dstdata += rowsize)
 memcpy(resizedimg.scanLine(ii), dstdata, rowsize);

QMenu

En lo particular me gusta tener un menú “Debug” en el cual puedo incorporar funcionalidades de depuración o que sólo deban estar presente durante el desarrollo, tales como recargar el fichero de estilos, por ejemplo. A fin de simplificar el desarrollo, este menú está siempre presente, y si la aplicación no está siendo compilada en modo depuración se quita.

Hago mención de dicho proceso acá porque no me fue evidente encontrar el cómo se hacía, así que lo dejo por escrito.

ui.menubar-&gt;removeAction(ui.menuDebug-&gt;menuAction());

Otro detalle sobre los menús es cómo establecer su color de fondo y de texto. Acá dejo un stylesheet de ejemplo.

/* Barra de menu */
QMenuBar
{
 background-image: url(:/someimage.jpeg);
}

/* Elementos de la barra de menú: título de cada menú */
QMenuBar::item
{
 background-color: transparent;
 color: #999999;
}
QMenuBar::item:selected
{
 background-color: transparent;
 color: #ffffff;
}

/* Cada menú */
QMenu
{
 background-color: #444444;
 color: #999999;
}

/* Cada acción del menú */
QMenu::item
{
 background-color: #444444;
}
QMenu::item:selected
{
 color: #ffffff;
}

Anec-notas (II)

Esta semana ha sido bastante movida ya que tenemos la entrega de un producto para la semana que viene, ya sabéis, afinar detalles y esas cosas, así que en esta segunda emisión de Anec-notas tendremos un par de puntos prácticos sobre Qt.

QLineEdit

El control QLineEdit es probablemente de los más utilizados a la hora de crear formularios. Normalmente se fija su contenido con un QLineEdit::setText(QString) al cargar el formulario, y se lee de vuelta su valor con un (QString)QLineEdit::text() al cerrar o validar el mismo. Lo que ocurre en este caso es que el flujo de la aplicación no establece puntos “finales” claros en los que validar u obtener el contenido del control (es decir, no hay un botón “Aceptar” o “Guardar”), sino que debe hacerse continuamente (cada vez que hay un cambio), guardando el contenido a la base de datos.

Con este fin es posible utilizar las señales textChanged(QString) y textEdit(QString), que se emiten cada vez que el contenido del control es modificado. La principal diferencia entre ellas radica en que textEdit sólo se emite cuando el contenido del control es modificado desde la interfaz (por el usuario), mientras que textChanged es emitida también si se cambia programáticamente mediante setText(QString). Para evitar que cada vez que inicialice el control tenga que escribir en la base de datos (operación innecesaria ya que el contenido en ese momento es el mismo), se utiliza la señal textEdit(QString).

Menús contextuales

Cuando se despliega un menú contextual, éste se apropia del mouse (grabMouse) lo que evita que los widgets que han solicitado seguir continuamente el movimiento del cursor no reciban el evento mientras el menú contextual esté desplegado. Como ejemplo práctico, imaginemos un widget que renderiza elementos gráficos propios, como un widget de OpenGL, y que mediante eventos MouseMove cambia el estado “highlighted” de los elementos que están debajo del cursor. Al desplegar el menú contextual para ese elemento, el widget deja de recibir el evento de MouseMove, por lo que cuando se oculta el menú contextual, el mouse puede estar en otra posición, resultando en una incoherencia entre el elemento resaltado y el cursor.

Los menús tienen una señal, aboutToHide(), que se emite cuando el menú se oculta, bien sea por seleccionar una entrada del menú o por desactivar el menú. Con esta señal se puede actualizar el estado de los elementos forzando un evento MouseMove. Ahora bien, este evento se emite antes de llamar al método asociado a la entrada del menú, por lo que si éste necesita del estado actual (por ejemplo, el elemento resaltado) debe tenerse este hecho en cuenta.

Anec-notas (I)

Con este post me gustaría comenzar una nueva sección en el blog la cual, por eso de ponerle nombre a todo, he decidido llamar “anec-notas”. Pretendo acá combinar historias técnicas de la semana, problemas resueltos y tópicos que pueda comentar públicamente sin romper cláusulas de confidencialidad, tratando a su vez de hacer un diario personal el cual consultar en un futuro. La idea no es hacer tutoriales completos sobre cada tema, sino contar detalles sobre ellos, así que no haré grandes introducciones en la medida de lo posible. Esta sección se inaugura con una historia de la semana pasada en el trabajo.

Detalles sobre funciones y métodos con parámetros variables

Para usar argumentos variables en C y C++ (antes de la última versión) se escriben unos puntos suspensivos y se usa un tipo de dato especial llamado va_list.

void foo(int arg1, ...)
{
 va_list args;
 va_start(args, arg1);

 float a = va_arg(args, float);
 printf("%f\n", a);

 va_end(args);
}

Básicamente, va_list es un puntero al stack, que es donde están almacenados los parámetros de la función. Con la función va_arg, junto al tipo de dato esperado, se van obteniendo cada uno de los parámetros de forma consecutiva. Por este motivo, es necesario conocer la posición del primer parámetro, por lo que es obligatorio que haya al menos un parámetro formal en la función, el cual se usa en la inicialización.

Todo esto surgió cuando tuve que diseñar una biblioteca de control de cámaras de vídeo. Esta biblioteca debe controlar varios modelos de cámara, cada una con propiedades diferentes. Para simplificar el diseño de la clase, se decidió utilizar dos métodos del tipo SetProperty(int prop, …) y GetProperty(int prop, …).

Hay dos detalles que me gustarían comentar. El primero es respecto al código de arriba: es incorrecto. Al llamar a la función ‘foo’, cualquiera sea el valor que se pase como segundo parámetro, siempre se mostrará 0.0. Esto me tuvo de cabeza un rato cuando trataba de procesar la propiedad FPS, que es un float. Esto se debe a que va_args interpreta los floats como doubles. La forma correcta sería:

 float a = (float)va_arg(args, double);

Esto no pasa con los punteros a float (float*), ya que todos los punteros son realmente el mismo tipo de dato, sólo que el compilador los interpreta de una forma u otra únicamente al desreferenciar la dirección de memoria.

El segundo detalle es que no es posible utilizar parámetros variables en funciones virtuales. Me explico, se puede, pero una clase que quiera reimplementar dicho método no tendrá forma de pasarle los parámetros variables. Esto llevaba a que no podía hacer una jerarquía de clases que me permitiera implementar el control de las propiedades más comunes en clases generales y dejar las propiedades específicas de cada cámara en especializaciones progresivas. La forma más sencilla de resolver este problema es utilizar dos métodos, uno propio de la clase padre que extrae los parámetros variables, y otro virtual, posiblemente privado, que recibe un va_list y que es quien realmente los interpreta:

class A
{
public:
 void foo(int n, ...)
 {
 va_list args;
 va_start(args, n);
 _bar(n, args);
 va_end(args);
 }

protected:
 virtual void _bar(int n, va_list args) = 0;
};

class B : public A
{
protected:
 virtual void _bar(int n, va_list args)
 {
 // ...
 }
};

En este caso es importante saber que una vez procesados los parámetros variables, no es posible recuperarlos, por lo que hay que tenerlo en cuenta en caso de que los métodos deban ser llamados a lo largo de la jerarquía de clases.

¡Espero verlos la semana que viene con más anec-notas!