SDCC vs z88dk: Comparando tamaño y velocidad de los binarios generados para Amstrad CPC

Click here to see in English

En este tutorial vamos a comparar los dos compiladores de C más famosos para Amstrad (desde el PC, claro). Vamos a comparar tanto el tamaño del binario que generan como de su velocidad de ejecución, haciendo varias pruebas/medidas diferentes. En el momento de hacer este tutorial, las versiones oficiales de los dos compiladores son z88dk v1.9 y SDCC 3.1.0. He tratado de probar varias compilaciones recientes de z88dk (Nightly builds/snapshots) pero he de decir que son muy inestables (al menos para Amstrad CPC) generando cuelgues o resultados inesperados, por lo que finalmente he decidido hacer la comparativa con las versiones oficiales.

Para hacer las medidas de tiempo de ejecución podríamos usar el comando del firmware del Amstrad CPC llamado KL TIME PLEASE (BD0D), pero como ya vimos en el tutorial Midiendo tiempos y optimizando Campo de estrellas 2D (C con SDCC) esto no funciona en z88dk ya que las interrupciones están siempre deshabilitadas al arrancar nuestro programa... Para las medidas de velocidad de ejecución usaremos un emulador grabando la ejecución en video para luego desde un editor contar fácilmente el tiempo por ejemplo entre dos textos mostrados en pantalla. Nota: Para los tamaños vamos a compararlos sin incluir la cabecera Amsdos.

Primer test, nos creamos dos variables enteras sin signo de 16 bits y hacemos un for de 65535 iteraciones mostrando un texto antes y después del bucle, el código fuente es exactamente el mismo en SDCC y en z88dk:

////////////////////////////////////////////////////////////////////////
// Test01sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>

main()
{
  unsigned int nCounter = 0;
  unsigned int nLoops = 0;
  
  printf("Start\n\r");

  for(nCounter = 0; nCounter < 65535; nCounter++)
    nLoops++;

  printf("End %u\n\r", nLoops);
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test01z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>

main()
{
  unsigned int nCounter = 0;
  unsigned int nLoops = 0;
  
  printf("Start\n\r");

  for(nCounter = 0; nCounter < 65535; nCounter++)
    nLoops++;

  printf("End %u\n\r", nLoops);
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos en ambos compiladores de la manera que ya hemos visto en anteriores tutoriales:

Para SDCC (Test01sdcc.bat):
sdcc -mz80 --code-loc 0x0138 --data-loc 0 --no-std-crt0 crt0.rel putchar.rel Test01sdcc.c
hex2bin Test01sdcc.ihx
cpcdiskxp -File Test01sdcc.bin -AddAmsdosHeader 100 -AddToNewDsk Test01sdcc.dsk

Para z88dk (Test01z88dk.bat):
zcc +cpc -m -notemp -o Test01z88dk.bin Test01z88dk.c -create-app -lndos -Ca-v -O2
cpcdiskxp -File Test01z88dk.cpc -AddToNewDsk Test01z88dk.dsk

Los dos binarios generan la misma salida en la pantalla del Amstrad CPC:

cpc

Grabamos en video con el emulador las dos ejecuciones y comparamos:

  SDCC z88dk
Tamaño 3616 bytes 1449 bytes
Velocidad 580 ms 4120 ms

El binario de SDCC ocupa más del doble que el z88dk pero se ejecuta 7 veces más rápido, o lo que es lo mismo, el binario de z88dk ocupa menos de la mitad que el de SDCC pero se ejecuta 7 veces más lento. A la vista de estos resultados tan diferentes en un programa tan simple solo podemos decir: ¡Pero que cojones es esto! :-)

Tras analizar un poco el ensamblador y el mapa de memoria que genera cada compilador llegamos a la conclusión que la gran diferencia de tamaño en este caso nos la da el 'printf' de la librería de C, en este caso se ve que z88dk la tiene mucho más optimizada (al menos en tamaño) que la de SDCC. Para comparar mejor con nuestro programa el código que genera únicamente de nuestro código fuente, vamos a quitar los 'printf' y los vamos a sustituir por una simple llamada al comando del firmware TXT_OUTPUT (BB5A), quedando así:

////////////////////////////////////////////////////////////////////////
// Test02sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned int nCounter = 0;
  unsigned int nLoops = 0;
  
  __asm
    ld a, #83 ;'S'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;

  for(nCounter = 0; nCounter < 65535; nCounter++)
    nLoops++;

  __asm
    ld a, #69 ;'E'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test02z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned int nCounter = 0;
  unsigned int nLoops = 0;
  
  #asm
    ld a, 83 ;'S'
    call $BB5A ;TXT_OUTPUT
  #endasm

  for(nCounter = 0; nCounter < 65535; nCounter++)
    nLoops++;

  #asm
    ld a, 69 ;'E'
    call $BB5A ;TXT_OUTPUT
  #endasm
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos, ejecutamos ambos programas y obtenemos los siguientes resultados:

  SDCC z88dk
Tamaño 83 bytes 153 bytes
Velocidad 580 ms 4120 ms

Como puede verse, para este programa en C tan simple de apenas 20 lineas, z88dk ha generado un binario que ocupa prácticamente el doble que el generado por SDCC y en cuanto a la velocidad, el binario de z88dk tarda 7 veces lo que el generado por SDCC. En resumen, en este ejemplo concreto SDCC es 'espectacularmente' mejor que z88dk.

Si analizamos el código fuente en ensamblador que generan ambos compiladores, vemos claramente las diferencias. Vamos a analizar únicamente el bloque que generan con el bucle for:

for(nCounter = 0; nCounter < 65535; nCounter++)
    nLoops++;

SDCC:

  ld  de,#0x0000
  ld  bc,#0xFFFF
00106$:
  inc de
  dec bc
  ld  a,b
  or  a,c
  jr  NZ,00106$

z88dk:

  ld  hl,0  ;const
  pop de
  pop bc
  push  hl
  push  de
  jp  i_5
.i_3
  ld  hl,2  ;const
  add hl,sp
  push  hl
  call  l_gint  ;
  inc hl
  pop de
  call  l_pint
  dec hl
.i_5
  ld  hl,2  ;const
  add hl,sp
  call  l_gint  ;
  push  hl
  ld  hl,65535  ;const
  pop de
  call  l_ult
  jp  nc,i_4
  ld  hl,0  ;const
  add hl,sp
  push  hl
  call  l_gint  ;
  inc hl
  pop de
  call  l_pint
  dec hl
  jp  i_3
.i_4

Para empezar se aprecia una clara diferencia de tamaño, el código que genera SDCC es claro y directo, usa un par de registros de 16bit (de y bc) y opera directamente con ellos (inc, dec) finalmente comparación y salto relativo hasta cumplir las iteraciones. Si observamos el código generado por z88dk vemos que hay un poco de desorden, cuesta seguirlo y el mayor problema es que está continuamente usando la pila (push, pop) y llamadas a funciones externas incomprensiblemente (l_gint, l_pint, l_ult) de ahí la enorme diferencia de rendimiento.

Vamos a modificar el programa para que use únicamente enteros de 8 bits, a ver si hay cambios en los resultados. Los programas quedarían de la siguiente forma:

////////////////////////////////////////////////////////////////////////
// Test03sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned char nCounter1 = 0;
  unsigned char nCounter2 = 0;
  unsigned char nLoops = 0;
  
  __asm
    ld a, #83 ;'S'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;

  for(nCounter1 = 0; nCounter1 <= 254; nCounter1++)
    for(nCounter2 = 0; nCounter2 <= 254; nCounter2++)
      nLoops++;

  __asm
    ld a, #69 ;'E'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test03z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned char nCounter1 = 0;
  unsigned char nCounter2 = 0;
  unsigned char nLoops = 0;
  
  #asm
    ld a, 83 ;'S'
    call $BB5A ;TXT_OUTPUT
  #endasm

  for(nCounter1 = 0; nCounter1 <= 254; nCounter1++)
    for(nCounter2 = 0; nCounter2 <= 254; nCounter2++)
      nLoops++;

  #asm
    ld a, 69 ;'E'
    call $BB5A ;TXT_OUTPUT
  #endasm
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos, ejecutamos ambos programas y obtenemos los siguientes resultados:

  SDCC z88dk
Tamaño 91 bytes 228 bytes
Velocidad 440 ms 2960 ms

Pues los resultados no varían mucho respecto a la prueba anterior, el binario generado por SDCC ocupa menos de la mitad del tamaño generado por z88dk y además ejecuta en casi la 7 parte del tiempo que el generado por z88dk. Nuevamente SDCC gana en esta prueba y con mucha diferencia.

Vamos a modificar un poco el programa para que use un bucle while y una variable de 32bits long, a ver como se comportan los compiladores, los programas quedarían así:

////////////////////////////////////////////////////////////////////////
// Test04sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned long nCounter = 0;
  
  __asm
    ld a, #83 ;'S'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;

  while(nCounter < 131070L)
    nCounter++;

  __asm
    ld a, #69 ;'E'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test04z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
main()
{
  unsigned long nCounter = 0;
  
  #asm
    ld a, 83 ;'S'
    call $BB5A ;TXT_OUTPUT
  #endasm

  while(nCounter < 131070L)
    nCounter++;

  #asm
    ld a, 69 ;'E'
    call $BB5A ;TXT_OUTPUT
  #endasm
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos, ejecutamos ambos programas y obtenemos los siguientes resultados:

  SDCC z88dk
Tamaño 105 bytes 251 bytes
Velocidad 2520 ms A los 27000 ms se resetea el CPC o se corrompe la pantalla

Pues parece que z88dk debe tener 'roto' el soporte de variables long (de 32bits) ya que no es capaz de compilar/generar bien este programa tan simple, otro punto negativo para z88dk.

Vamos a probar ahora con un algoritmo 'famoso' como es el algoritmo de ordenación quicksort (fuente) y le vamos a hacer ordenar 960 enteros de 16 bits. Los programas quedarian de la siguiente forma:

////////////////////////////////////////////////////////////////////////
// Test05sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////

void quicksort(int* data, int N)
{
  int i, j;
  int v, t;
 
  if( N <= 1 )
    return;
 
  // Partition elements
  v = data[0];
  i = 0;
  j = N;
  for(;;)
  {
    while(data[++i] < v && i < N) { }
    while(data[--j] > v) { }
    if( i >= j )
      break;
    t = data[i];
    data[i] = data[j];
    data[j] = t;
  }
  t = data[i-1];
  data[i-1] = data[0];
  data[0] = t;
  quicksort(data, i-1);
  quicksort(data+i, N-i);
}

const int aNumbers[960] = {
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    };

main()
{
  __asm
    ld a, #83 ;'S'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;

  quicksort(aNumbers, 960);

  __asm
    ld a, #69 ;'E'
    call #0xBB5A ;TXT_OUTPUT
  __endasm;

  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test05z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////

void quicksort(int* data, int N)
{
  int i, j;
  int v, t;
 
  if( N <= 1 )
    return;
 
  // Partition elements
  v = data[0];
  i = 0;
  j = N;
  for(;;)
  {
    while(data[++i] < v && i < N) { }
    while(data[--j] > v) { }
    if( i >= j )
      break;
    t = data[i];
    data[i] = data[j];
    data[j] = t;
  }
  t = data[i-1];
  data[i-1] = data[0];
  data[0] = t;
  quicksort(data, i-1);
  quicksort(data+i, N-i);
}

static int aNumbers[960] = {
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    12, 67, 123, 456, 789, 98, 64, 43, 32, 1, 4, 9, 345, 444, 777,
                    };

main()
{
  #asm
    ld a, 83 ;'S'
    call $BB5A ;TXT_OUTPUT
  #endasm

  quicksort(aNumbers, 960);

  #asm
    ld a, 69 ;'E'
    call $BB5A ;TXT_OUTPUT
  #endasm
  
  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos, ejecutamos ambos programas y obtenemos los siguientes resultados (esta vez, restamos los 960 enteros al tamaño para limitarnos al tamaño que genera del algoritmo, no de los datos):

  SDCC z88dk
Tamaño 590 bytes 617 bytes
Velocidad 1840 ms 2620 ms

Pues esta vez la diferencia de tamaño es menor, aunque sigue ganando SDCC. En cuanto a la ejecución, SDCC tarda 780 ms menos en ordenar los 960 numeros, o lo que es lo mismo, SDCC es un 30% más rápido que z88dk.

Como prueba final, vamos a compilar un programa un poco más complejo y grande, que pinte en pantalla varias lineas usando el famoso algoritmo de Bresenham y las funciones de pintar en pantalla que se hicieron para el tutorial Pintando pixeles: Introducción a la memoria de video. El programa es exactamente igual para SDCC que para z88dk, diferenciándose únicamente en las dos lineas de código en ensamblador para poner el modo 0:

////////////////////////////////////////////////////////////////////////
// Test06sdcc.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////

void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = *pByteAddress;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  *pByteAddress = nByte;
}

void PutPixelMode0(unsigned char nX, unsigned char nY, unsigned char nColor)
{
  unsigned char nPixel = 0;
  unsigned int nAddress = 0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2);
  nPixel = nX % 2;

  SetMode0PixelColor((unsigned char *)nAddress, nColor, nPixel);
}

/**
 * Draws a line between two points p1(p1x,p1y) and p2(p2x,p2y).
 * This function is based on the Bresenham's line algorithm and is highly 
 * optimized to be able to draw lines very quickly. There is no floating point 
 * arithmetic nor multiplications and divisions involved. Only addition, 
 * subtraction and bit shifting are used. 
 *
 * Note that you have to define your own customized setPixel(x,y) function, 
 * which essentially lights a pixel on the screen.
 */
void swap(int n1, int n2)
{
  int nAux = n1;
  n1 = n2;
  n2 = nAux;
}
void lineBresenham(int p1x, int p1y, int p2x, int p2y)
{
    int F, x, y;
    int dy;
    int dx;
    int dy2;
    int dx2;
    int dy2_minus_dx2;
    int dy2_plus_dx2;

    if (p1x > p2x)  // Swap points if p1 is on the right of p2
    {
        swap(p1x, p2x);
        swap(p1y, p2y);
    }

    // Handle trivial cases separately for algorithm speed up.
    // Trivial case 1: m = +/-INF (Vertical line)
    if (p1x == p2x)
    {
        if (p1y > p2y)  // Swap y-coordinates if p1 is above p2
        {
            swap(p1y, p2y);
        }

        x = p1x;
        y = p1y;
        while (y <= p2y)
        {
            PutPixelMode0(x, y, 1);
            y++;
        }
        return;
    }
    // Trivial case 2: m = 0 (Horizontal line)
    else if (p1y == p2y)
    {
        x = p1x;
        y = p1y;

        while (x <= p2x)
        {
            PutPixelMode0(x, y, 1);
            x++;
        }
        return;
    }


    dy            = p2y - p1y;  // y-increment from p1 to p2
    dx            = p2x - p1x;  // x-increment from p1 to p2
    dy2           = (dy << 1);  // dy << 1 == 2*dy
    dx2           = (dx << 1);
    dy2_minus_dx2 = dy2 - dx2;  // precompute constant for speed up
    dy2_plus_dx2  = dy2 + dx2;


    if (dy >= 0)    // m >= 0
    {
        // Case 1: 0 <= m <= 1 (Original case)
        if (dy <= dx)   
        {
            F = dy2 - dx;    // initial F

            x = p1x;
            y = p1y;
            while (x <= p2x)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dy2;
                }
                else
                {
                    y++;
                    F += dy2_minus_dx2;
                }
                x++;
            }
        }
        // Case 2: 1 < m < INF (Mirror about y=x line
        // replace all dy by dx and dx by dy)
        else
        {
            F = dx2 - dy;    // initial F

            y = p1y;
            x = p1x;
            while (y <= p2y)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dx2;
                }
                else
                {
                    x++;
                    F -= dy2_minus_dx2;
                }
                y++;
            }
        }
    }
    else    // m < 0
    {
        // Case 3: -1 <= m < 0 (Mirror about x-axis, replace all dy by -dy)
        if (dx >= -dy)
        {
            F = -dy2 - dx;    // initial F

            x = p1x;
            y = p1y;
            while (x <= p2x)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F -= dy2;
                }
                else
                {
                    y--;
                    F -= dy2_plus_dx2;
                }
                x++;
            }
        }
        // Case 4: -INF < m < -1 (Mirror about x-axis and mirror 
        // about y=x line, replace all dx by -dy and dy by dx)
        else    
        {
            F = dx2 + dy;    // initial F

            y = p1y;
            x = p1x;
            while (y >= p2y)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dx2;
                }
                else
                {
                    x++;
                    F += dy2_plus_dx2;
                }
                y--;
            }
        }
    }
}

main()
{
  //SCR_SET_MODE 0
  __asm
    ld a, #0
    call #0xBC0E
  __endasm;

  lineBresenham(0, 0, 159, 199);
  lineBresenham(0, 199, 159, 0);
  lineBresenham(80, 0, 80, 199);
  lineBresenham(0, 100, 159, 100);

  while(1) {};
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Test06z88dk.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////

void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = *pByteAddress;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  *pByteAddress = nByte;
}

void PutPixelMode0(unsigned char nX, unsigned char nY, unsigned char nColor)
{
  unsigned char nPixel = 0;
  unsigned int nAddress = 0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2);
  nPixel = nX % 2;

  SetMode0PixelColor((unsigned char *)nAddress, nColor, nPixel);
}

/**
 * Draws a line between two points p1(p1x,p1y) and p2(p2x,p2y).
 * This function is based on the Bresenham's line algorithm and is highly 
 * optimized to be able to draw lines very quickly. There is no floating point 
 * arithmetic nor multiplications and divisions involved. Only addition, 
 * subtraction and bit shifting are used. 
 *
 * Note that you have to define your own customized setPixel(x,y) function, 
 * which essentially lights a pixel on the screen.
 */
void swap(int n1, int n2)
{
  int nAux = n1;
  n1 = n2;
  n2 = nAux;
}
void lineBresenham(int p1x, int p1y, int p2x, int p2y)
{
    int F, x, y;
    int dy;
    int dx;
    int dy2;
    int dx2;
    int dy2_minus_dx2;
    int dy2_plus_dx2;

    if (p1x > p2x)  // Swap points if p1 is on the right of p2
    {
        swap(p1x, p2x);
        swap(p1y, p2y);
    }

    // Handle trivial cases separately for algorithm speed up.
    // Trivial case 1: m = +/-INF (Vertical line)
    if (p1x == p2x)
    {
        if (p1y > p2y)  // Swap y-coordinates if p1 is above p2
        {
            swap(p1y, p2y);
        }

        x = p1x;
        y = p1y;
        while (y <= p2y)
        {
            PutPixelMode0(x, y, 1);
            y++;
        }
        return;
    }
    // Trivial case 2: m = 0 (Horizontal line)
    else if (p1y == p2y)
    {
        x = p1x;
        y = p1y;

        while (x <= p2x)
        {
            PutPixelMode0(x, y, 1);
            x++;
        }
        return;
    }


    dy            = p2y - p1y;  // y-increment from p1 to p2
    dx            = p2x - p1x;  // x-increment from p1 to p2
    dy2           = (dy << 1);  // dy << 1 == 2*dy
    dx2           = (dx << 1);
    dy2_minus_dx2 = dy2 - dx2;  // precompute constant for speed up
    dy2_plus_dx2  = dy2 + dx2;


    if (dy >= 0)    // m >= 0
    {
        // Case 1: 0 <= m <= 1 (Original case)
        if (dy <= dx)   
        {
            F = dy2 - dx;    // initial F

            x = p1x;
            y = p1y;
            while (x <= p2x)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dy2;
                }
                else
                {
                    y++;
                    F += dy2_minus_dx2;
                }
                x++;
            }
        }
        // Case 2: 1 < m < INF (Mirror about y=x line
        // replace all dy by dx and dx by dy)
        else
        {
            F = dx2 - dy;    // initial F

            y = p1y;
            x = p1x;
            while (y <= p2y)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dx2;
                }
                else
                {
                    x++;
                    F -= dy2_minus_dx2;
                }
                y++;
            }
        }
    }
    else    // m < 0
    {
        // Case 3: -1 <= m < 0 (Mirror about x-axis, replace all dy by -dy)
        if (dx >= -dy)
        {
            F = -dy2 - dx;    // initial F

            x = p1x;
            y = p1y;
            while (x <= p2x)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F -= dy2;
                }
                else
                {
                    y--;
                    F -= dy2_plus_dx2;
                }
                x++;
            }
        }
        // Case 4: -INF < m < -1 (Mirror about x-axis and mirror 
        // about y=x line, replace all dx by -dy and dy by dx)
        else    
        {
            F = dx2 + dy;    // initial F

            y = p1y;
            x = p1x;
            while (y >= p2y)
            {
                PutPixelMode0(x, y, 1);
                if (F <= 0)
                {
                    F += dx2;
                }
                else
                {
                    x++;
                    F += dy2_plus_dx2;
                }
                y--;
            }
        }
    }
}

main()
{
  //SCR_SET_MODE 0
  #asm
    ld a, 0
    call $BC0E
  #endasm

  lineBresenham(0, 0, 159, 199);
  lineBresenham(0, 199, 159, 0);
  lineBresenham(80, 0, 80, 199);
  lineBresenham(0, 100, 159, 100);

  while(1) {};
}
////////////////////////////////////////////////////////////////////////

Compilamos, ejecutamos ambos programas y obtenemos los siguientes resultados:

  SDCC z88dk
Tamaño 1546 bytes 2091 bytes
Velocidad 320 ms 1960 ms

SDCC vuelve a ganar y con una diferencia aplastante de nuevo, el binario de SDCC se ejecuta 6 veces más rápido que el de z88dk. Como una imagen vale más que mil palabras, he aquí una comparación visual:

SDCC z88dk
sdcc z88dk

Como la versión z88dk v1.9 es del año 2009 he decidido probar a compilar el último ejemplo con una beta reciente para ver si han mejorado algo en estos años, concretamente he usado la beta del día 2012-05-19, estos son los resultados:

  SDCC v3.1.0 z88dk v1.9 z88dk 2012-05-19
Tamaño 1546 bytes 2091 bytes 2050 bytes
Velocidad 320 ms 1960 ms 2120 ms

No ha cambiado apenas la cosa....

 

ACTUALIZADO: el 09/07/2012 ha sido liberada la version 3.2.0 de SDCC, vamos a ver si la cosa ha mejorado o empeorado:

  SDCC v3.1.0 z88dk v1.9 z88dk 2012-05-19 SDCC v3.2.0
Tamaño 1546 bytes 2091 bytes 2050 bytes 1497 bytes
Velocidad 320 ms 1960 ms 2120 ms 260 ms

¡¡Ha mejorado aun más!!

 

ACTUALIZADO: el 06/11/2012 ha sido liberada la version 1.10 de z88dk, vamos a ver si la cosa ha mejorado o empeorado:

  SDCC v3.1.0 z88dk v1.9 z88dk 2012-05-19 SDCC v3.2.0 z88dk v1.10
Tamaño 1546 bytes 2091 bytes 2050 bytes 1497 bytes 2178 bytes
Velocidad 320 ms 1960 ms 2120 ms 260 ms 2100ms

Como vemos, con la nueva version de z88dk no ha cambiado mucho la cosa respecto a la versión v1.9, los resultados siguen siendo penosos comparandolo con SDCC.

 

ACTUALIZADO: El 20/05/2013 ha sido liberada la version 3.3.0 de SDCC, vamos a ver si la cosa ha mejorado o empeorado:

  SDCC v3.1.0 z88dk v1.9 z88dk 2012-05-19 SDCC v3.2.0 z88dk v1.10 SDCC v3.3.0
Tamaño 1546 bytes 2091 bytes 2050 bytes 1497 bytes 2178 bytes 1427 bytes
Velocidad 320 ms 1960 ms 2120 ms 260 ms 2100ms 300 ms

 

Conclusiones:

  • Estabilidad:
    1. z88dk ha fallado (en ejecución) al usar variables de 32bits, dejando colgado el cpc
    2. Las pruebas con las versiones beta de z88dk son muy inestables
    3. Las interrupciones están siempre deshabilitadas en z88dk al arrancar nuestro programa, causando que algunas funciones del firmware no funcionen correctamente como KL TIME PLEASE (BD0D), este fallo ya ha sido notificado y aceptado por el equipo de desarrollo del z88dk y se comporta de diferente manera en la version 1.8, la 1.9 y las betas actuales.
    4. No ha surgido ningún problema con las compilaciones de SDCC y las interrupciones están habilitadas al arrancar nuestro programa, pudiendo usar el reloj de sistema.
  • Velocidad:
    1. SDCC ha generado código más rápido en todos los casos, hasta 6 y 7 veces más rápido que el generado por z88dk.
  • Tamaño:
    1. SDCC ha generado código más pequeño en casi todas las pruebas.
    2. z88dk parece tener más optimizada (en tamaño) las funciones de la librería estándar de c (al menos printf).

A la vista de estos resultados tan abrumadores yo me despido definitivamente de z88dk:

No malgastéis con z88dk vuestro tiempo (ciclos de cpu :-)) , ¡¡Larga vida a SDCC!!

 

Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: SDCC_vs_z88dk.zip

 

www.CPCMania.com 2012