1.08 Lezione 8 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki

Sincronizzazione in RISC

Il compilatore usato è il gcc. Il gcc è un progetto open source realizzato da GNU che tra i vari progetti ha anche creato questo compilatore.

Su questi tool è fondato il sistema LINUX.

Sulle basi della lezione di ieri, per eseguire un programma scritto in C, ci basta scrivere a linea di comando:

gcc hello.c	

Con questo comando viene creato un file a.out eseguibile; possiamo verificarlo scrivendo sul terminale:

ls -l

Con questo comando vengono listati tutti i files in una directory, ecco l'output:

ls -l
total 72
-rwxr-xr-x  1 folly  staff  8432 11 Gen 20:03 a.out

Capiamo che il file è eseguibile non dall'estensione come nei sistemi microsoft, ma dalle "x" all'inizio della stampa.

Per eseguirlo digitiamo sulla Shell:

./a.out

GCC su Mac OS

Nelle ultime versioni di MacOS, gli sviluppatori hanno deciso di passare ad un compilatore gcc più moderno, chiamato clang.

Se continuiamo ad usare degli script che usano gcc, viene invece usato il compilatore clang.

File oggetto

Lanciando il gcc otteniamo anche un file oggetto, con estensione .o che deve essere opportunamente linkato alle librerie, in modo da ottenere un eseguibile a.out.

Dimensioni dei files

Se digitiamo sul terminale:

ls -la	

Otteniamo:

MBP-di-Giuliano:FromCtoAssembly folly$ ls -la
total 72
drwxr-xr-x  7 folly  staff   224 11 Gen 20:04 .
drwxr-xr-x  3 folly  staff    96 11 Gen 19:41 ..
-rwxr-xr-x  1 folly  staff  8432 11 Gen 20:03 a.out
-rw-r--r--  1 folly  staff    84 11 Gen 19:41 helloWorld.c
-rw-r--r--  1 folly  staff   784 11 Gen 19:57 helloWorld.o
-rw-r--r--  1 folly  staff   775 11 Gen 19:42 helloWorld.s
-rwxr-xr-x  1 folly  staff  8432 11 Gen 20:04 programmaEseguibile

Notiamo che il file a.out (eseguibile) è il file più grande.

Linking dinamico

Quando eseguiamo una compilazione viene eseguito un linking di tipo dinamico e non statico.

Se negli esempi precedenti (la compilazione) avessimo usato un linking di tipo statico, il file a.out eseguibile, invece di pesare 8432 byte, sarebbe pesato molto di più, proprio perchè il linker avrebbe aggiunto tutte le librerie utilizzate interamente.

Abbiamo quindi capito che il linker dinamico linka le librerie solo quando il programma è in esecuzione. Quindi all'interno del file a.out non è presente il codice delle librerie, ma viene linkata al momento dell'esecuzione.

Il vantaggio

Evita che il programma eseguibile diventi enorme, e le librerie vengono aggiunte siolo quando servono.

Inoltre in questo modo le librerie vengono aggiornate automaticamente, proprio perchè non sono incluse nel codice (hardwired).

Lazy linkage

Il linkaggio non viene eseguito a momento della compilazione, ma quando il programma viene lanciato in esecuzione.

Quando eseguo il programma, alla prima chiamata (ad esempio della printf) si salta non alla funzione ma al cosiddetto stub il quale chiede al SO di caricare in un particolare indirizzo, l'indirizzo della funzione che si vuole chiamare.

Nei sistemi UNIX abbiamo delle librerie dinamiche (a linking dinamico) .so, mentre quelle a linking statico sono .a.

L'estensione so sta per shared object. Microsoft invece chiama queste librerie dinamiche .dll.

Avviare applicazioni java

Cosa succede quando lancio un programma java?

La catena di operazioni è completamente diversa da quella appena vista.

Anche in Java esiste un compilatore: Javac; questo compilatore non produce file assembly ne tantomeno files .o. Quello che viene in realtà prodotto dal compilatore javac è il cosiddetto java bytecode; questo perchè java è nato per consentire la portabilità su sistemi diversi, e non era inteso per applicazioni in programmi particolarmente grandi (come in realtà accade).

Questo java bytecode è progettato appositamente per essere particolarmente portabile; questo bytecode viene interpretato al volo sulla macchina su cui ci troviamo.

Basta quindi cambiare la Java Virtual Machine per cambiare il modo in cui il bytecode viene interpretato, in modo da poterlo eseguire su processori e sistemi operativi diversi.

Un Interprete è quindi molto diverso da un compilatore, infatti l'interprete interpreta istruzione per istruzione, e questo compito è svolto dalla java virtual machine. Grazie a questo meccanismo è possibile eseguire gli stessi già compilati .jar su diversi processori, senza doverli compilare nativamente sulla macchina su cui devono essere eseguiti.

Lo svantaggio

Questo tipo di compilazione (interprete) non è allo stesso livello di un compilatore vero e proprio, infatti questo meccanismo è molto meno efficiente rispetto, ad esempio, ad un programma scritto in C.

Per tentare di risolvere il problema, il runtime di java quando si rende conto che ci sono dei pezzi di codice che vengono eseguiti abbastanza spesso, entra automaticamente in funzione un compilatore just in time; solo quel pezzo di codice viene (durante l'esecuzione) compilato (quindi a codice macchina) in modo da non dover interpretare continuamente lo stesso codice.

Questo permette quindi a java di raggiungere prestazioni simili a quelle di un compilatore C.

🏁 0:33 03-24

Esempio di ordinamento

Programma scritto in C

La tecnica usata è il bubble sort (che schifo).

void swap(int k, int v[]){
  int temp;
  temp = v[k]
  v[k] = v[k+1];
  v[k+1] = temp;
}

funzione swap

void sort(int v[], size n){
	size i, j;
  for(i = 0; i<n; i+=1){
    for(j = i-1; j >=0 && v[j] > v[j+1]; j-=1)
      swap(v,j);
  }

}

Programma scritto in Assembly

In Assembly i registri sono stati "preparati" nel seguente modo:

  • v in a0
  • k in a1
  • Temp in t0
swap: 
	slli t1, a1, 3	# moltiplicazione per 8
	add t1, a0, t1 # posiziono il puntatore sulla posizione dell'array (a sinistra); in t1 ho v[k] (indirizzo)
	ld t0, 8(t1)	# 
	sd t2, 0(t1)
	sd t0, 8(t1)
	
	jalr zero, 0(ra)

funzione di Swap

Funzione principale : loop esterno

  • For(i = 0; i<n; i+=1)
	li s3, 0
for1tst:
	bge s3, a1, exit1
	
	

🏁 00:44

Effetti dell'ottimizzazione del compilatore

I compilatori C sono in grado di eseguire delle ottimizzazioni del codice, spostando opportunamente pezzi di codice, eliminando pezzi inutili, ecc.

Il programma viene quindi rimaneggiato spostando istruzioni, sostituendo alcune, ecc, in modo da far girare il codice più velocemente.

Come abilitiamo le ottimizzazioni?

E' possibile indicare sulla lina di comando gcc -o + numero.

Se digitiamo nel terminale man clang (su UNIX) possiamo consultare le varie informazioni della compilazione.

Alcune di queste opzioni, potrebbero produrre un programma che non funziona correttamente (anche se solitamente non accade)

Velocità

Ad esempio, con il nostro programma di bubble sort visto prima, solo aggiungendo l'ottimizzazione quando compiliamo, è possibile ottenere un programma ottimizzato che gira notevolmente più veloce.

Linee di codice

Oltre alla velocità, si può avere anche un ottimizzazione di istruzioni eseguite, che anche in questo caso si traducono in un'ottimizzazione a livello di velocità.

Numero di clock

Il compilatore può anche decidre di cambiare le istruzioni che abbiamo usato, in modo che le istruzioni da esso scelte vengano eseguite con un numero di cicli di clock minore.

La pratica di cambiare le istruzioni utilizzate, da parte del compilatore, è chiamata Strenght reduction.

Questa pratica è poco effettiva su processori di livello avanzato come quelli odierni, ma su un processore "vecchio" porta dei grandi vantaggi in termine di tempo (ad esempio sostituire le moltiplicazioni per potenza di 2 con degli shift).

Pulire un array in assembly

  mv t0, a0

  slli t1, a1, 3
  add t2, a0, t1

loop2:
	sd zero, 0(t0)
	addi t0, t0, 8
	bltu t0, t2, loop2

Codice ottimizzato

Nel loop è presente semplicemente un incremento di 8 per passare alla doubleword successiva dopo aver azzerato l'indirizzo di memoria corrente.

Istruzioni MIPS

Le istruzioni del MIPS sono molto simili a quelle del RISC. Anche in questo caso abbiamo istruzioni a 32 bit, con 32 registri, alla memoria si accede solo mediante load e store, quindi anche in questo caso non possiamo eseguire direttamente dalla memoria.

Sul RISC, però, sono state aggiunte le istruzioni di branch come: blt, bge, bltu, bgeu.

Anche la codifica delle istruzioni è molto simile.


Fine lezione 8

⚠️ **GitHub.com Fallback** ⚠️