AngoLinux

Programma che valuta le prestazione della CPU in vari modi

- A cura del Prof. Stefano Salvi -


Cominciamo ad analizzare le prestazioni dei processori, ed a scontrarci con le nuove architetture.

Il testo dell'esercizio è il seguente:

Scrivere un programma in C/Assembly che misuri le prestazioni del processore in termini di MIPS e MFLOPS, utilizzando istruzioni di somma intere a 32 bit per i MIPS (sia in c che direttamente in assembler) e double per i MFLOPS.

NOTE:

  • Per introdurre del codice assembler in un sorgente C si può usare l'istruzione __asm__ ("<codice assembler>").
    "<codice assembler>" verrà passato direttamente all'assembler, quindi dovrà conetenre dei \n per separare le righe delle istruzioni, ed eventualmente del \t per indentare il sorgente.
    Dato che il codice scritto viene eseguito nel mezzo di una routine C, che utilizza i registri, è opportuno che il nostro codice salvi i registri prima di usarli e li ripristini prima di terminare.
  • Per misurare la durata di un evento si può utilizzare la funzione clock_t clock(void);, che ritorna una misura del tempo impiegato dal processo che lo richiama.
    Per ottenere una misura in secondi del tempo, occorre dividere il numero ottenuto da clock per la costante CLOCKS_PER_SEC.
    Per sapere la durata di una certa operazione, basta memorizzare il valore ritornato da clock prima di iniziare l'operazione e sottrarlo al valore ritornato dalla stessa funzione alla fine dell'operazione.
    Naturalmente la risoluzione della funzione clock non è sufficiente per misurare la durata di una sola somma, quindi occorrerà ripetere molte volte la aomma per avere una misura siginficativa.
  • Ripetere le operazioni per arrivare ad un tempo di 5 - 10 secondi, per minimizzare gli errori.
  • Per avere una misura abbastanza ripetibile del tempo è meglio fare in modo di iniziare la misura sempre all'inizio di un time slice. Per questo è possibile richiamare lo scheduler tramite la funzione int sched_yield(void);.
  • Si vuole misurare il solo tempo di una somma. Visto che questa somma andrà ripetuta un congruo numero di volte, si userà un ciclo.
    Occorre eliminare dal conto il tempo impiegato per le istruzioni del ciclo. Per fare questo si può in primo luogo inserire in ogni ciclo più somme.
    Inoltre si può fare la misura due volte, inserendo nel ciclo un numero diverso di somme. La differenza tra le due misure sarà il tempo impiegato per le sole somme in più. Naturalmente la misura andrà divisa per il numero di somme misurate.
  • L'architettura superscalar avanzata dei nuovi processori fa si che la differenza tra cicli con un diverso numero di somme dia risultati abbastanza imprvedibili, in base alle ottimizzazioni che il processorte riesce ad apportare.

Una possibile soluzione è la seguente:
/* mips_mflops.c
 * Stefano Salvi - 4/10/02
 * Benchmark che calcola:
 *  - MIPS basandosi su di un ciclo in C;
 *  - MIPS basandosi su di un ciclo in Assembler;
 *  - MFLOPS basandosi su di un ciclo in C
 */
#include <stdio.h>
#include <time.h>
#include <sched.h>

#define DIM_MIPS	1000000000	/*1 miliardo - Ripetizioni per MIPS*/
#define DIM_MFLOPS	100000000	/*100 milioni - Ripetizioni per MFLOPS*/

/* Calcolo MIPS con ciclo ASSEMBLY
 * Esegue un ciclo che fa' la somma di una costante a 2 registri,
 * quindi ripete l'operazione con 3 registri.
 * Calcola il tempo per ciascuno dei due cicli.
 * Calcola la differenza, che dovrebbe essere il tempo impiegato per una somma per ciclo
 * divide per il numero di ripetizione ed ottiene il tempo di una ripetizione.
 */
void asm_mips()
{
clock_t inizio;		// Tempo iniziale Ciclo
clock_t	tempoDueAdd;	// Tempo ciclo con due somme
clock_t tempoTreAdd;	// Tempo ciclo con tre somme
double tempo;		// Tempo in secondi
double MIPS;

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale primo ciclo

  __asm__(
    "pushl %ecx\n"		// Salva tutti i registri usati
    "\tpushl %ebx\n"
    "\tpushl %eax\n"
    "\tpushl %edx\n"
    "\tmovl $1000000000,%ecx\n"	// Inizializza contatore
    "\tmovl $1,%eax\n"		// Inizializza variabili
    "\tmovl $2,%ebx\n"
    "\tmovl $3,%edx\n"
"lp:\n"
    "\taddl $1,%eax\n"		// Prima somma
    "\taddl $1,%ebx\n"		// Seconda somma
    "\tdecl %ecx\n"		// Decrementa contatore
    "\tjnz lp\n"		// Ciclo finche' decremento non da' 0
    "\tpopl %edx\n"		// Ripristina i registri 'sporcati'
    "\tpopl %eax\n"		// Ripristina i registri 'sporcati'
    "\tpopl %ebx\n"		// Ripristina i registri 'sporcati'
    "\tpopl %ecx\n"
  );

  tempoDueAdd = clock()- inizio; // Tempo ciclo con due somme

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale secondo ciclo

  __asm__(
    "pushl %ecx\n"		// Salva i registri usati
    "\tpushl %ebx\n"
    "\tpushl %eax\n"
    "\tpushl %edx\n"
    "\tmovl $1000000000,%ecx\n"	// Inizializza contatore
    "\tmovl $1,%eax\n"		// Inizializza variabili
    "\tmovl $2,%ebx\n"
    "\tmovl $3,%edx\n"
    "\tmovl $0,%ebx\n"		// Inizializza variabile
"lp1:\n"
    "\taddl $1,%eax\n"		// Prima somma
    "\taddl $1,%ebx\n"		// Seconda somma
    "\taddl $1,%edx\n"		// Terza Somma
    "\tdecl %ecx\n"		// decrementaConteggio cicli
    "\tjne  lp1\n"		// Ciclo
    "\tpopl %edx\n"		// Ripristina i registri usati
    "\tpopl %eax\n"
    "\tpopl %ebx\n"
    "\tpopl %ecx\n"
  );

  tempoTreAdd=clock() - inizio; // Tempo ciclo con tre somme

  printf("tempi: 2 somme %f secondi, tre somme %f secondi\n",
    ((double)tempoDueAdd)/CLOCKS_PER_SEC, ((double)tempoTreAdd)/CLOCKS_PER_SEC);

  /* sottraendo al tempo del secondo ciclo il tempo del primo si otterra' il tempo puro
   * dell'incremento b++; */

  tempo = (tempoTreAdd - tempoDueAdd) / (double)CLOCKS_PER_SEC;
	
  printf("Il tempo di %d somme e' %f secondi\n", DIM_MIPS, tempo);
	
  /*Quante istruzioni fa al secondo = MIPS*/
  MIPS=(DIM_MIPS/tempo)/1000000;	
  printf("\nMIPS: %lf\n",MIPS);
}

/* Calcola i MIPS con un ciclo in C
 * Esegue un ciclo che fa' la somma di una costante a 2 variabili,
 * quindi ripete l'operazione con 3 registri.
 * Calcola il tempo per ciascuno dei due cicli.
 * Calcola la differenza, che dovrebbe essere il tempo impiegato per una somma per ciclo
 * divide per il numero di ripetizione ed ottiene il tempo di una ripetizione.
 */
void c_mips()
{
static int a,b,c,i;
clock_t inizio;		// Tempo iniziale Ciclo
clock_t	tempoDueAdd;	// Tempo ciclo con due somme
clock_t tempoTreAdd;	// Tempo ciclo con tre somme
double tempo;		// Tempo in secondi
double MIPS;

  a=0;				// Inizializza le variabili
  b=0;
  c=0;

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale primo ciclo

  for(i=0;i < DIM_MIPS;i++) 	// primo ciclo con due somme
  {
    a+=3;
    b+=4;
  }

  tempoDueAdd = clock()- inizio; // Tempo ciclo con due somme

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale secondo ciclo

  for(i=0;i<DIM_MIPS;i++)	//secondo ciclo con tre somme
  {	
    a+=2;
    b+=3;
    c+=4;
  }
	
  tempoTreAdd=clock() - inizio; // Tempo ciclo con tre somme

  printf("tempi: 2 somme %f secondi, tre somme %f secondi\n",
    ((double)tempoDueAdd)/CLOCKS_PER_SEC, ((double)tempoTreAdd)/CLOCKS_PER_SEC);

  /* sottraendo al tempo del secondo ciclo il tempo del primo si otterra' il tempo puro
   * dell'incremento b++; */

  tempo = (tempoTreAdd - tempoDueAdd) / (double)CLOCKS_PER_SEC;
	
  printf("Il tempo di %d somme e' %f secondi\n", DIM_MIPS, tempo);
	
  /*Quante istruzioni fa al secondo = MIPS*/
  MIPS=(DIM_MIPS/tempo)/1000000;	
  printf("\nMIPS: %lf\n",MIPS);
}

/* Calcolo dei MFLOPS
 * Esegue un ciclo che fa' la somma di una costante a 2 variabili double,
 * quindi ripete l'operazione con 3 registri.
 * Calcola il tempo per ciascuno dei due cicli.
 * Calcola la differenza, che dovrebbe essere il tempo impiegato per una somma per ciclo
 * divide per il numero di ripetizione ed ottiene il tempo di una ripetizione.
 */
void c_mflops() /*Calcolo MFLOPS*/
{
static int i;
clock_t inizio;		// Tempo iniziale Ciclo
clock_t	tempoDueAdd;	// Tempo ciclo con due somme
clock_t tempoTreAdd;	// Tempo ciclo con tre somme
double tempo;		// Tempo in secondi
double MFLOPS;
double a,b,c;

  a=0.0;		// Inizializza le variabili
  b=0.0;
  c=0.0;

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale primo ciclo

  for(i=0;i<DIM_MFLOPS;i++) /*primo ciclo con una istruzione*/
  {
    a=a+0.1;
    b=b+0.1;
  }

  tempoDueAdd = clock()- inizio; // Tempo ciclo con due somme

  sched_yield();		// Cede il passo, cosi' dopo lo scheduler non lo disturba

  inizio = clock();		// tempo iniziale secondo ciclo

  for(i=0;i<DIM_MFLOPS;i++) /*secondo ciclo con due istruzioni*/
  {	
    a=a+0.1;
    b=b+0.1;
    c=c+0.1;
  }
	
	
  tempoTreAdd=clock() - inizio; // Tempo ciclo con tre somme

  printf("tempi: 2 somme %f secondi, tre somme %f secondi\n",
    ((double)tempoDueAdd)/CLOCKS_PER_SEC, ((double)tempoTreAdd)/CLOCKS_PER_SEC);

  /* sottraendo al tempo del secondo ciclo il tempo del primo si otterra' il tempo puro
   * dell'incremento b++; */

  tempo = (tempoTreAdd - tempoDueAdd) / (double)CLOCKS_PER_SEC;
	
  printf("Il tempo di %d somme e' %f secondi\n", DIM_MFLOPS, tempo);
	
  /*Quante istruzioni fa al secondo = MFLOPS*/
  MFLOPS=(DIM_MFLOPS/tempo)/1000000;	
  printf("\nMFLOPS: %lf\n",MFLOPS);
}

int main()
{
int scelta;

  // Stampa il menu'
  printf("1. MIPS con C\n");
  printf("2. MIPS con ASM\n");
  printf("3. MFLOPS\n");
  printf("Inserisci la scelta: ");

  // Legge la scelta
  scanf("%d",&scelta);

  // Esegue il lavoro
  switch(scelta)
  {
    case 1:
      c_mips();
      break;

    case 2:
      asm_mips();
      break;

    case 3:
      c_mflops();
      break;

    default:
      printf("La scelta non e' presente!");
      break;
  }

  return 0;
}

Per provare il programma, scaricare il sorgente, compilarlo con il comando cc mips_mflops.c -o mips_mflops ed eseguirlo con il comando ./mips_mflops.


[Home Page dell'ITIS "Fermi"] [Indice Quarta] [Precedente] [Successivo]

© Ing. Stefano Salvi - Released under GPL licence