Esta entrada es la continuacón directa de esta otra.
Ya lo he dicho antes, hay aplicaciones que resultan absurdas realizadas en C++, y también otras que también lo son cuando están hechas con otros lenguajes. Imaginaros un Autocad o un Office que estuviera íntegramente escrito en .NET. Si ya nos quejamos del rendimiento y la velocidad de carga de ellos, ya no os digo si en lugar de ser código nativo fuera .NET, o peor aún, Java (No le deseo ningún mal a Java, pero es lento de cojones, y las aplicaciones que he visto hechas con él también lo son. Ignoro si se debe al propio lenguaje o a la posible incompetencia de los programadores, pero el hecho final es que es lento).
Pues bien, en mi caso, C++ es el lenguaje que más objetivos cumple respecto a mis necesidades: programas industriales de uso interno a la empresa (para la cadena de producción y para testear productos), programas de demostración de nuestros productos, simuladores, pedazos de código de demo, firmware para nuestros cacharros y firmware de interoperabilidad entre el PC y el aparato (cuando no es el PC el propio aparato), y muy de tarde en tarde alguna aplicación completa o casi completa para algún producto de terceros cuando el producto no tiene nada que ver con nuestra línea principal de productos (es decir, si nos dedicáramos a hacer máquinas para tostar pipas de girasol, esa aplicación podría ser para un mando a distancia).
Como mucho código está compartido entre el PC y el firmware, lo ideal es C++, y C cuando no haya otra posibilidad. Además, la mayoría de cosas que hago está relacionada con protocolos de comunicaciones, y el PC entra para la simulación y verificación de los mismos.
Os pongo un ejemplo. Hace unos meses tuve que implementar una biblioteca aritmética de enteros de 64 bits para un micro de 8 bits. Es decir, teníamos que realizar operaciones entre enteros de 64 bits dentro de un micro de 8 bits, y a lo más que llegaba el compilador con más posibilidades era a trabajar con enteros de 32. No es que ese fuera el objetivo del firmware, sino que varias de sus tareas exigían el uso de ese tipo de operaciones, así como convertir cadenas de texto en enteros y viceversa. La solución impedía el uso de C++ y de algunas clases que hay circulando por ahí, así que era necesaria una implementación con dos enteros de 32 bits unidos en una estructura, todo ello en C y ocupando el menor tamaño de código posible y con la ejecución más rápida.
¿Cómo habríais resuelto vosotros el tema? Una posibilidad era ir probando en el micro mediante el proceso de editar código, compilar en plataforma cruzada, grabar en el micro y depurar con el JTAG… Laborioso, ¿no? Además, está el problema de cómo comprobar si las operaciones han sido correctas o no.
Ahí es donde entra el PC de lleno. La solución consistió en hacer un programa en estricto C (para que luego el compilador embebido tragara sin problemas) y mezclarlo con código en C++ que, aprovechando que el PC sí que es capaz de trabajar con enteros de 64 bits, comprobara la salida del otro código. Otro problema es el dominio de la aplicación. Si no compruebo todas las combinaciones posibles pudiera ocurrir que el programa fallara con alguna combinación de números, por lo que se requería una prueba bastante exhaustiva aunque no completa porque no es viable comprobar todas las combinaciones aritméticas (suma, resta, multiplicación y división) de cualesquiera dos enteros de 64 bits. Entre las comprobaciones hay que tener en cuenta las operaciones entre dos números muy pequeños, dos muy grandes con y sin desbordamiento, uno grande y uno pequeño en ambos lados, también con y sin desbordamiento, y sobre todo en la franja en la que el resultado y los operandos están en el entorno del salto de los que caben en 32 bits a los más bajos de 64…
Dicho y hecho, tras unos cuatro mil trillones de números generados aleatoriamente con conocimiento de causa (siguiendo las reglas descritas arriba) y un fin de semana de un Q4 al 100% de CPU, comprobamos que la biblioteca no tenía errores o estaba más lo más cerca posible de no tenerlos, de hecho descubrimos código profesional que hay suelto por ahí que en determinadas circunstancias falla, pero esa es otra historia.
Una vez colocado en el micro junto al resto del firmware comprobamos que… ¡fallaba miserablemente! De hecho era completamente incapaz de generar una sola operación correcta. Tras una investigación descubrimos que lo que fallaba no era nuestro programa, sino la biblioteca de enteros de 32 bits del compilador y el propio compilador, que no era capaz de trabajar ni con una estructura de dos enteros de 16 bits lado a lado ni con sus punteros. Esa también es otra historia que no viene al caso y que se solucionó simplificando el código (separando operaciones complejas de multiplicación más suma de enteros de 32 bits en operaciones más sencillas con datos intermedios) y usando variables globales separadas en lugar de estructuras.
En resumen, para que eso funcionara tuvimos que tirar a la basura los conceptos de ocultación de datos, genericidad y abstracción. Hicimos un proyecto de demo con los problemas del compilador y de la biblioteca, se lo enviamos al fabricante del compilador y… ¿Habéis recibido vosotros respuesta? Nosotros tampoco.
Esta entrada continuará la semana que viene.
Lo que sucede Rafa es que C++ y C son lenguajes especialmente buenos para trabajar en el dominio que vos describís… cualquier otro lenguaje lleva todas las de perder en ese campo y por lo tanto toda comparación con otros lenguajes es cuando menos necia e injusta.
Es como comparar Ruby con C para crear aplicaiones web o, comparar XAML/CSharp con C++ para crear applicaciones de escritorio….
Java es lento per sé (de entrada, ni el java compilado es rápido, así que un java interpretado ni te cuento). Yo leí un papel de una universidad americana que decía que si en un programa en java querías hacer algo que requiriera un proceso intensivo, para eso tienes jni que te hace de interfaz por ejemplo con C 😀 (y lo he sufrido en mis carnes 🙁 )