Hace unos años redacté Sieve en C, una macro-comparativa del rendimiento de diferentes compiladores de C/C++ para Windows ejecutando una implementación de la Criba de Eratóstenes o Sieve. Unos días atrás publiqué Visual C++ 2008 vs Visual C++ 2017, y volvió a picarme el gusanillo sobre lo que ha evolucionado la calidad del código generado por los compiladores.

A ello se ha sumado el anuncio por parte de Walter Bright que DigitalMars C++ es ahora código libre. DigitalMars es la continuación del clásico Symantec C++, y a su vez del legendario Zortech C++.



El proceso ha sido bastante sencillo. He partido de mi experiencia con MEMTRACE para así tener una base multi-plataforma y multi-compilador, y entonces he generado los diferentes ejecutables de Sieve. La única dificultad ha sido que no todos los compiladores funcionaban sobre Windows, de manera que hay dos cadenas de compilación: builddos (Borland y Microsoft) y buildwin para el resto (Watcom, OpenWatcom y DigitalMars).

Como también quería verificar el grado de evolución de los compiladores, cuando ha sido posible he incluido diferentes versiones. Por ejemplo Borland C++ 3.1 de 1991 y Borland C++ 5.02 de 1997 o Watcom C++ 11.0 de 1997 y OpenWatcom C++ 2.0 beta de 2018.

Para los que vivisteis esa época, recordaréis lo complicado que era obtener software. La mayoría de programadores aficionados y muchos profesionales usábamos Turbo C y sus sucesores. Unos pocos, usaban Microsoft C Compiler una estupenda herramienta que nunca llegó a tener un buen IDE. Sabíamos que Borland no era el mejor producto ni de lejos. La calidad de su código, que empezó siendo excelente, fue quedándose estancada con el paso del tiempo. A su favor tenía que era fácil de conseguir, casi omnipresente, que tenía un IDE estupendo, y que se acompañaba de una completísima ayuda en linea.

Era la década de 1990, no había aparecido DJGPP y lo poco que existía de GCC era para Linux. Sin embargo, siempre hubo un producto que estuvo en mi mente: Watcom C++. Una herramienta carísima, pero que permitía compilación cruzada. Se ejecutaba sobre DOS, Windows u OS/2, y generaba programas compatibles con esas plataformas además de Novell. Imaginaros lo que aquello representaba en esa época. Desde un modesto equipo corriendo sobre MS-DOS, era posible desarrollar y compilar para Windows y viceversa.

Pero Watcom tenía otra cosa mucho más atractiva. Y es que era el líder en cuanto a rendimiento de sus programas. Combinado con el extensor DOS/4GW, era la herramienta que veíamos en software comercial de máximo nivel, sobre todo en juegos (Doom, Quake, …). Las malas lenguas decía que la propia IBM había probado convertir parte del código de OS/2 a Watcom, y habían conseguido un 50% de mejoría.

Sea como fuere, cuando logré hacerme con Watcom C++ 9.5, y un equipo suficientemente potente para ejecutarlo, ya había pasado demasiado tiempo, lo cual no impidió que me hiciera un gran fan de él, y que como sabéis, haya colaborado y apoyado el desarrollo del mismo desde que se hiciera libre. En aquel momento me decepcionó la falta de un IDE para DOS

Para las versiones de 32 bits para DOS es obligatorio el uso de un extensor DOS. En DigitalMars he utilizado FlashTek que es el que trae de serie. Para Watcom/OpenWatcom, aunque me iba a decantar por DOS32A, finalmente escogí el reducido PMODE/WI.

Estos han sido los resultados:

CompiladorPlataformaTamaño ejecutable (bytes)Tiempo de ejecución (ms)
Borland C++ 3.1DOS 167.0645.166
Borland C++ 5.02DOS 1634.3925.770
Visual C++ 1.52 (Microsoft C++ Compiler 8.0)DOS 168.6374.720
Watcom C++ 11.0cDOS 1617.3906.124
OpenWatcom C++ 2.0 betaDOS 169.1486.036
Watcom C++ 11.0cDOS 3244.8566.087
OpenWatcom C++ 2.0 betaDOS 3227.7805.913
DigitalMars C++ 8.57DOS 1613.6286.480
DigitalMars C++ 8.57DOS 3231.5856.150

Podemos apreciar que la mejoría del 50% no es tal. Soy el primero en defender a Watcom y conozco bien los detalles del código que genera. Se que es de estupenda calidad, pero como siempre, esas ventajas se obtienen en casos concretos, especialmente cuando se pueden hacer accesos de 32 bits. Ello quiere decir que en general, es necesario ajustar ciertas partes del código para obtener ese elevado beneficio. Si nos ponemos en el contexto de los juegos, con enormes accesos a la memoria gráfica, es ahí en donde el modo protegido de Watcom brillaría.

Lo que sorprende es que el “odiado” Borland C++ 3.1 acaba siendo el líder. Sus ejecutables son los más ligeros, y también los más rápidos. Cuando hablamos de programas sencillos, cuanta menos complejidad tenga el compilador y su librería, mucho mejor. El ejemplo se ve claramente al compararlo con su sucesor, Borland C++ 5.02, el ejecutable es mucho más grande debido a las nuevas librerías, u además también más lento. Esto nos recuerda aquellos tiempos oscuros de Borland.

Otra cosa que me gusta es el avance que ha tenido Watcom desde haberse convertido en un proyecto libre con OpenWatcom. Los ejecutables son más pequeños gracias a la mayor segregación de funciones de la librería. Pero además, son también algo más rápidos. Un aspecto en el que he contribuido ligeramente, pero del que hay que agradecer sobre todo a Jiří Malák y Michael Necasek.

Se confirma lo que explicaba al principio con Microsoft C Compiler, pese a algunos bugs en el generador de código, en modo de 16 bits solía ser el más eficiente. Recuerdo cuando probé las versión 6.00 que me dejó impresionado en ese sentido. Finalmente, no puedo omitir a DigitalMars, tan poco conocido en Europa que no fue hasta que fuera código libre que tuviera la oportunidad de probarlo y de darme cuenta de todo lo que me había perdido.

Por otro lado, tengo algunas dudas acerca de la emulación que realiza DOSBox, por lo que los tiempos deben tomarse con cautela.

Me da un poco de vergüenza poner el código aquí, porque es una conversión rápida del que tenía hecho en Javascript, pero no importa:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
 
#define ITER 1000
 
void main(void)
{
	register unsigned int k,i;
	unsigned int iter, count;
	static unsigned char flags[ITER];
	clock_t start, end;
	unsigned long t;
 
	start=clock();
	for(iter=1; iter<=ITER; iter++)
	{
		count=0;
		for(i=0; i<=ITER; i++)
		{
			flags[i]=0;
		}
		for(i=2; i<=ITER; i++)
		{
			if (flags[i]==0)	/* found a prime */
			{
				count++;	
				for (k=i+i; k<=ITER; k+=i)
				{
					flags[k]=1;
				}
			}
		}
	}
	end=clock();
	t=(long)(end-start)*1000/(int)CLOCKS_PER_SEC;
	printf("%d iterations %d primes found, %ld ms.", ITER, count, t);
}

Como siempre, te dejo un paquetito con el código fuente y todos los binarios por si quieres probarlo aquí (107 Kb. en formato ZIP).