Un programmatore solitamente ha a che fare nella sua carriera con svariati linguaggi di
programmazione. La tecnologia di oggi dà ai programmatori una serie di tools che ci
assiste lungo tutta la catena produttiva nascondendo nello stesso tempo tutti gli step ed i
prodotti intermedi della programmazione. Diciamo quindi che se non si è un
programmatore che si occupa della scrittura di driver o di codice di sistema di basso
livello, difficilmente si avrà la possibilità di vedere il codice Assembly generato dal
compilatore. Eppure oggi l’evoluzione più interessante che investe il mondo dei compilatori
si gioca proprio sulla capacità di generare codice Assembly sempre più ottimizzato
soprattutto nell’ottica di un diffondersi sempre più massivo di processori per dispositivi
mobili e che quindi dispongono di risorse più limitate rispetto ad un computer tradizionale.
Chi programma da un po’ di tempo sicuramente avrà studiato (all’università o per conto
suo) un po’ di Assembly per processori come il mitico Z80 o il Motorola 68000
(ricordiamoci che nel 2011 si sono festeggiati i primi quarant’anni del primo processore
Intel 4004) e quindi parole come “mov” o “jmp” suoneranno abbastanza famigliari. Come
dicevo prima, l’evoluzione dei “code generator” di molti compilatori è più che mai viva.
Qualche esempio pratico. Consideriamo il seguente frammento di codice (sample.c):
struct Array
{
size_t Size;
double *Data;
}
void MyFunc (struct Array *A)
{
for(size_t i = 0; i < A->Size; i++)
{
A->Data[i] += i;
}
}
Proviamo a vedere che tipo di Assembly genera un compilatore di ultima generazione
come LLVM. Per fare questo scriviamo da linea di comando: clang -c -O2 -S sample.c che
genera un file sample.s contenete il codice Assembly. Se diamo un’occhiata a questo file
troveremo qualcosa del genere circa la sezione di codice che descrive il ciclo for:
## BB#1:
xorl %eax,%eax
movsd LCPI0_0(%rip), %xmm0
LBB0_2: ## => This Inner Loop Header
movq 8(%rdi), %rci ## load A->Data
movsd (%rcx, %rax,8), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rcx, %rax,8)
incq %rax
movq (%rdi), %rdx ## load A->Size
cmpq %rdx, %rax
jb LLB0_2
In questo caso il compilatore, senza ulteriori indicazioni, si mantiene su una posizione
molto conservativa e solida ricaricando ad ogni ciclo sia il puntatore alla size, sia il
puntatore ai dati. Noi sappiamo però dalla teoria del linguaggio che due puntatori a tipi di
dato diversi non possono essere uno l’alias dell’altro, non è quindi necessario ricaricare i
due puntatori ad ogni ciclo. Diciamo questo al compilatore LLVM scrivendo:
clang -c -O2 -fstric-aliasing -S sample.c
Così facendo otteniamo un Assembly così generato (sample.s)
## BB#1:
xorl %eax,%eax
movsd LCPI0_0(%rip), %xmm0
movq 8(%rdi), %rci ## load A->Data
movq (%rdi), %rdx ## load A->Size
LBB0_2: ## => This Inner Loop Header
movsd (%rcx, %rax,8), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rcx, %rax,8)
incq %rax
cmpq %rdx, %rax
jb LLB0_2
Questo incrementa le performance del codice generato di quasi il 30% su codice
contenete molti cicli.
Altro esempio. LLVM dalla versione 3.0 ha una funzionalità che si chiama “Instruction
Scheduler” che consente di ottimizzare, ordinandole, le istruzioni Assembly in base ai
tempi di ciclo in modo da non tenere in sospeso delle istruzioni durante l’esecuzione di
istruzioni più pesanti. Meglio presentare un esempio per capirci meglio.
Consideriamo questa porzione di Assembly generata dal compilatore GNU gcc (versione
4.2):
r2 = add r0, r1 ## 1 ciclo
r3 = load [addr] ## 2 cicli
// aspetta un ciclo
r4 = sub r2, r3 ## 1 ciclo
La stessa porzione di Assembly generata dal compilatore LLVM (versione 3.0) risulta
invece essere:
r3 = load [addr] ## 2 cicli
r2 = add r0, r1 ## 1 ciclo
r4 = sub r2, r3 ## 1 ciclo
qui non si ha alcuna attesa e quindi il codice eseguibile è decisamente più performante.
Concludendo è sul campo di battaglia del codice che mai nessuno vede che si sta
combattendo la guerra per scrivere programmi ed applicazioni sempre più veloci ed in
grado di cavalcare l’onda dell’evoluzione hardware. Un’altra evoluzione paradigmatica
importante è CUDA, ma di questo ne parleremo un’altra volta.
Luca Ciciriello

Nessun commento:
Posta un commento