Newsgroup: it.comp.lang.c++
Titolo: Ancora sul discorso dell'efficienza

 ottimizzazioni usate).
Che ne pensate?
EMG
--
EMAG Software Homepage:     
http://www.emagsoftware.it
The EMG's ZX-Spectrum Page: 
http://www.emagsoftware.it/spectrum
The Best of Spectrum Games: 
http://www.emagsoftware.it/tbosg

 Che ne pensate?
Che i casi da valutare diventano dei "casi a se" e che quindi non ci si puo'
pronunciare in generale.

 > deve aggiungere l'offset di "variabile" al puntatore this.
Cosi' come e' scritto non ha assolutamente senso. Il puntatore "this"
in C++ e' semplicemente un "trucco" sintattico per potersi riferire
esplicitamente all'oggetto corrente da una funzione membro della sua
classe, dal momento che tale oggetto non e' rappresentato
esplicitamente. Scrivere
class A {
  public:
    int ii;
};
A a;
a.ii = 2;
e
struct B {
  int ii;
};
B b;
b.ii = 3;
E' assolutamente equivalente. Forse quello che voleva dire il tuo amico
e' che se ho ad esempio:
class A {
  public:
    void f(int i);
  private:
    int ii;
};
void A::f(int i) { ii = i; }  // (1)
Il compilatore per individuare la posizione del membro dato ii al punto
(1) deve aggiungere il suo spiazzamento al valore corrente del
puntatore this.
In effetti la funzione membro corrisponde al seguente pseudocodice:
void f_A(A * const this; int i) { this->ii = i; }
Questo pero' non e' molto diverso da
struct B {
  int ii;
};
void g(int i) {
  B b;
  b.ii = i;
}
Perche' anche in questo caso il compilatore non conosce a priori la
posizione di b e deve quindi effettuare un calcolo per individuare b.ii
. Ecco perche' nella pratica le differenze non sono evidenti.
Ciao,
Nicola

 deve aggiungere l'offset di "variabile" al puntatore this.
fosse completamente generale. Ma vedo che Nicola dice che è errata e
questo mi conforta (sull'argomento mi fido più di Nicola che di quella
persona :-) ).


 Cosi' come e' scritto non ha assolutamente senso.
Non capisco perché mi sono preso questa gatta da pelare. A questo punto
direi che o quella persona interviene precisando il suo punto di vista e
difendendolo oppure possiamo concludere qui il thread facendo finta che
non sia successo niente. :-)


Ciao a tutti!
Volevo fare qualche precisazione sul caso specifico che stavamo analizzando 
insieme ad Enrico;
data la particolarita' del caso non so quanto possa interessare questo 
newsgroup, ma visto che
siamo stati tirati in ballo... :)
Premetto che non sono un esperto di nulla, e che l'argomento non e' affatto 
un confronto tra le
prestazioni del C e quelle del C++. Il caso e' assolutamente "di nicchia" e 
quindi ha valore
solo in se' o per poco piu', ma e' su questo che ero stato interpellato per 
soddisfare una
curiosita'.
Mi dispiace anche di dover tirare in ballo l'assembler, ma quando si parla 
di velocita' del codice
compilato e' su quello che alla fine si va a ragionare...
Inoltre il post e' lunghissimo, scusate fin da ora la prolissita'... :)
Sono uno degli autori di un emulatore di ZX Spectrum [1], un vecchio 
computer a 8-bit basato su
microprocessore Z80; la prima versione dell'emulatore era scritta in C, 
mentre adesso stiamo
lavorando ad un progetto successivo scritto invece in C++ (ci fa assai 
comodo!).
Trattandosi di un'applicazione realtime piuttosto complessa che deve girare 
anche su macchine
di fascia piu' bassa possibile, uno dei nostri obiettivi principali e' 
sempre stata
l'ottimizzazione spinta del core Z80, ovvero il componente che incide per la 
maggior quota
sulle prestazioni globali dell'emulazione (escluso il rendering della 
grafica sullo schermo).
In breve, avevamo la necessita' che la CPU fosse modellata con una classe 
C++, ad esempio per
l'istanziazione multipla, ma facciamo conto che questo sia un requisito di 
progetto. Le
funzioni membro operano su dei dati (i registri emulati del processore) 
raggruppati per
comodita' in una grossa struct, e siccome queste operazioni sono 
frequentissime, ottimizzare
nel nostro caso significa essenzialmente ridurre al minimo i costi 
computazionali degli accessi
in memoria per manipolare queste variabili; le elaborazioni vere e proprie 
sono gia' ridotte al
minimo possibile :) Quindi vogliamo decidere in base al numero di 
letture/scritture in memoria
che compaiono caso per caso nel codice compilato finale.
Sulla base dei miei ricordi universitari riguardo alla traduzione del codice 
C++ in
assembler da parte di un compilatore, ho preferito mettere la struttura 
fuori dalla classe in
una variabile globale allocata staticamente, in quanto in questa maniera 
l'indirizzo dei vari membri e' gia' noto al
compilatore a compile-time e quindi viene usata una singola istruzione MOV 
per prelevarne il
contenuto. Se invece nel nostro emulatore la struttura viene dichiarata come 
variabile membro
della classe, il compilatore deve prima ottenere il puntatore this ai dati 
dell'istanza, e
quindi prelevare il dato come prima, per un totale di due accessi. 
Naturalmente non e' sempre
cosi' e sono possibili delle ottimizzazioni: nel caso di accessi 
ravvicinati, il valore del
this potrebbe essere conservato in un registro e riutilizzato per gli 
accessi successivi al
primo, ma sfortunatamente questa opportunita' si presenta raramente nel 
nostro caso a causa
della complessita' del codice; disassemblato alla mano, anche con le 
ottimizzazioni attivate il
compilatore e' costretto a inserire frequentemente una lettura di this dallo 
stack frame per
accedere ai vari dati.
Effettivamente la prima soluzione si e' rivelata per noi la piu' efficiente 
in azione con un margine del
10-20% su un Athlon 800 MHz, almeno secondo i nostri test di profiling 
(misurazione dei
cicli di clock necessari per l'esecuzione tramite l'istruzione assembler 
READTSC del processore).
Un margine cosi' inaspettatamente ampio ci ha sorpreso, ma lo ritengo
imputabile alla particolarissima natura del nostro programma. In 
un'applicazione
generica dubito che le differenze siano marcatamente misurabili, 
specialmente sui processori
attuali.
In fondo a questo lunghissimo post (scusatemi!!) ho riportato un programmino 
che illustra la
situazione che ho descritto. Su richiesta di Enrico ho aggiunto l'esame 
anche di un terzo caso,
in cui la struttura e' dichiarata come variabile locale di una funzione 
membro. Anche questo
caso e' equivalente al primo secondo i nostri criteri (cioe' basta una sola 
MOV) perche' le
variabili locali vengono riferite dal compilatore C/C++ usando l'indirizzo 
del record di
attivazione sullo stack (permanentemente al sicuro nel registro EBP a 
differenza di quanto
accade con this) a cui viene contestualmente sommato, nella stessa 
istruzione assembler,
l'offset della variabile locale che e' noto a priori al compilatore.
La classe dell'esempio possiede tre funzioni membro Execute1/2/3() che fanno 
la stessa cosa (un
assegnamento di variabile) nei tre modi diversi che ho detto sopra; in 
particolare si tratta
dello statement "Rx.ABUS=0x4000;". La variabile in questione e' la struttura 
Z80_CONTEXT (presa
pari pari dal nostro codice), che nel primo caso (R1) e' dichiarata 
globalmente (la soluzione
che abbiamo usato noi), nel secondo (R2) e' un membro della classe e nel 
terzo (R3) e' una
variabile locale alla funzione membro stessa.
Riporto il disassemblato del codice prodotto dal compilatore Visual C++ .NET
2003 (che e' quello che usiamo noi), ma vale anche con il GCC. Per aumentare 
la leggibilita' ho
omesso il prologo e l'epilogo delle procedure, lasciando solo il codice 
assembler relativo agli
assegnamenti in questione.
1) Execute1(): 1 MOV. Lavora sulla struttura R1 dichiarata come variabile 
globale (come avviene
nel nostro emulatore).
 R1.ABUS=0x4000; // accesso alla variabile globale
00411A53  mov         word ptr [R1+1Ah (42851Ah)],4000h
2) Execute2(): 2 MOV. Lavora sulla struttura R2 dichiarata come variabile 
membro della classe;
c'e' una lettura in memoria in piu'; in semplici routines il contenuto di 
EAX puo' essere
riutilizzato in seguito, ma se il codice e' complicato (come nel nostro 
caso) tutti i registri
vengono sporcati ed e' necessario ricaricare spesso il valore di this, anche 
con le
ottimizzazioni del compilatore.
 R2.ABUS=0x4000; // accesso alla variabile membro della classe
00411AF3  mov         eax,dword ptr [this]
00411AF6  mov         word ptr [eax+1Ah],4000h
3) Execute3(): 1 MOV. Lavora sulla struttura R3 dichiarata come variabile 
locale di una
funzione membro; analogo al primo caso come codice compilato.
 Z80_CONTEXT R3;
 R3.ABUS=0x4000; // accesso alla variabile locale alla funzione membro
004136F3  mov         word ptr [ebp-72h],4000h
Spero di non aver commesso errori, nel  caso segnalatemeli per favore!
Ci tengo a ribadire che questo non e' assolutamente un confronto di 
performance tra C e C++, ma
e' solo la cronaca di un caso particolarissimo che mi era stata richiesta 
per soddisfare una
curiosita' di Enrico sul funzionamento del nostro emulatore. Oltretutto 
penso che con i processori
attuali non valga piu' la pena di impazzire tanto perche' si guadagna sempre 
di meno, e anche i
compilatori sono sempre piu' efficienti.
Effettivamente noi siamo stati un po' maniaci nello spremere il piu' 
possibile dal computer, ma
agli utenti dell'emulatore che hanno ancora un Pentium MMX 266 questo fara' 
molto piacere :)
Ciao,
Luca
[1] RealSpectrum: 
http://www.ramsoft.bbk.org/realspec.html
---------------------------------------------------------------------------------------
// definizione di alcuni tipi per architetture a 32 bit
typedef unsigned char BYTE;
typedef unsigned short int WORD;
typedef unsigned int DWORD;
typedef union Z80Reg
{
 struct
 {
  BYTE l;
  BYTE h;
 };
 WORD W;
} Z80Reg; // union per l'accesso rapido sia come word che come MSB/LSB 
indipendenti
typedef struct Z80_CONTEXT
{
 Z80Reg AF;
 Z80Reg BC;
 Z80Reg DE;
 Z80Reg HL;
 Z80Reg IX;
 Z80Reg IY;
 Z80Reg IR;
 Z80Reg PC;
 Z80Reg SP;
 Z80Reg AF2;
 Z80Reg BC2;
 Z80Reg DE2;
 Z80Reg HL2;
 WORD ABUS;
 BYTE DBUS;
 BYTE NMI;
 BYTE NMITSustain;
 BYTE INT;
 BYTE IFF1;
 BYTE IFF2;
 BYTE HALT;
 BYTE IMode;
 BYTE M1;
 BYTE OPCODE;
 BYTE OPCODE_CB;
 BYTE OPCODE_ED;
 BYTE PREFIX;
 BYTE Type; // 0x00 = NMOS; 0xFF = CMOS
 BYTE Status;
 BYTE IR_01;
 WORD ICount;
 WORD IMax;
 DWORD TStates;
 DWORD Clock;
 DWORD TEmulStart;
 DWORD BreakPoint;
 BYTE BreakPointHit;
 void (*ReadMem)(void);
 void (*WriteMem)(void);
 void (*ReadMem16)(void);
 void (*WriteMem16)(void);
 void (*ReadPort)(void);
 void (*WritePort)(void);
 void (*EndFrame)(void);
 void (*NMIProc)(void);
 void (*Internal_1)(void);
 void (*Internal_2)(void);
 void (*Internal_4)(void);
 void (*Internal_5)(void);
 void (*Internal_7)(void);
 void (__fastcall *TrapHandler)(void);
} Z80_CONTEXT;
class Z80 {
protected:
 Z80_CONTEXT R2;
public:
 void Execute1(int t);
 void Execute2(int t);
 void Execute3(int t);
};
Z80_CONTEXT R1; // struttura globale
void Z80::Execute1(int t)
{
 R1.ABUS=0x4000; // accesso alla variabile globale
}
void Z80::Execute2(int t)
{
 R2.ABUS=0x4000; // accesso alla variabile membro della classe
}
void Z80::Execute3(int t)
{
 Z80_CONTEXT R3;
 R3.ABUS=0x4000; // accesso alla variabile locale alla funzione membro
}
Z80 cpu; // istanza globale della classe
int main()
{
 cpu.Execute1(100);
 cpu.Execute2(100);
 cpu.Execute3(100);
 return 0;
}

Interessante. In questi termini non stento a crederci. Mi aspetto che
la differenza tra il caso uno ed il caso due sia sostanzialmente
trascurabile nella maggior parte dei casi, ovviamente non nel vostro.
Il caso due dell'esempio potrebbe migliorare se cpu e' effettivamente
dichiarata nello stesso file del main() e se le funzioni membro Execute
sono definite inline. Mi rendo conto pero' che questa non e' una
soluzione sempre applicabile. 
Ciao,
Nicola

 indipendenti
Qui aggiungerei un membro alla union per farne il pad alla dimensione
più comoda per i registri della CPU target. Lo stesso discorso per la
Z80_CONTEXT.

 insieme ad Enrico;
Grande Luca! Sono contento che hai accettato la mia esortazione! :-)


 trascurabile nella maggior parte dei casi, ovviamente non nel vostro.
Potresti approfondire? In che senso la differenza sarebbe trascurabile
nella maggior parte dei casi? Insomma, ci dobbiamo preoccupare o no
della perdita di efficienza quando ci buttiamo nella OOP più sfrenata?


A mio avviso dobbiamo cominciare a preoccuparci solo quando misurazioni
specifiche identificano un collo di bottiglia che porta a prestazioni
non accettabili.
Stavo per aggiungere anche un esempio chiarificatore, ma mi sono reso
conto che in realta' penso che i compilatori di Luca non facciano il
massimo possibile. Rispondo a lui, comunque.
Ciao,
Nicola

 massimo possibile. Rispondo a lui, comunque.
Ok, grazie. Leggerň con interesse.


Come non detto, ci ho pensato su ancora un po' :-)
Riprendo il mio esempio. Date
struct A {
    void f(int i) { ii = i; }
    int ii;
};
e
void g(A & a, int i) { a.ii = i; }
Spero che siamo d'accordo che nelle implementazioni comuni A::f e g
sono del tutto equivalenti. In entrambe i casi il codice generato
corrisponde a mettere sullo stack l'indirizzo di a, mettere sullo stack
il valore di i e quindi calcolare l'indirizzo di a.ii . Ora lo
spiazzamento di ii all'interno di A e' un valore costante, ma
l'indirizzo di a no. E' quindi necessario caricare l'indirizzo di a
dallo stack in un registro e sommare lo spazzamento di ii e quindi
utilizzare il valore ottenuto come indirizzo della locazione di memoria
dove scrivere il valore di i prelevato dallo stack. Di qui i due
accessi di memoria di cui parlava Luca.
Nel caso invece di
void h(int i) {
  A a;
  a.ii = i;
}
Sia la posizione di a rispetto alla cima dello stack (contenuta nello
Stack Pointer), sia lo spiazzamento di ii all'interno di A sono valori
costanti ed e' sufficiente rispettivamente sommarli e sottrarli dallo
Stack Pointer (di fatto un'unica operazione) per individuare la
posizione di ii. Un solo accesso, per l'appunto a ii.
Nel caso poi della variabile globale la posizione di ii e' assoluta e
quindi non e' nenmmeno necessario fare il calcolo della sua posizione.
Resto convinto che nella stragrande maggioranza dei casi le differenze
siano assolutamente trascurabili, ma ovviamente se si e' alla ricerca
dell'efficienza estrema le cose possono cambiare.
Ciao,
Nicola

 punto interessante da chiarire.
No, non e' cosi'. Il caso di Luca e' particolare, perche' lui ha e
avra' sempre una singola istanza della classe/struct che rappresenta la
CPU e quindi puo' pemettersi di dichiararla globalmente. Appena pero'
hai almeno due istanze di una classe o riscrivi tutte le funzioni piu'
volte o devi essere parametrico.
Ciao,
Nicola

 EMG
Ciao,
In un certo senso si! Certo che c'è differenza di tempo e di istruzioni per
accedere a "var" se:
int var;
oppure:
class Classe {
 int var;
};
Sai già che per ogni oggetto che crei dalla class Classe ci sarà una diversa
variabile "var", una per ogni oggetto, perchè non è 'static'. Quindi a
run-time l'accesso deve avvenire con uno spiazzamento o dereferenziazione
rispetto al 'this' che punta lo specifico oggetto.
Ma del resto non puoi neanche dichiarare e usare la variabile 'var' come
globale perchè se ne hai bisogno di una per ogni oggetto non puoi fare a
meno di crearla come appartenente alla classe.
Mi sembra che il problema di Luca fosse proprio questo (riporto un estratto
dal suo post);
"Sulla base dei miei ricordi universitari riguardo alla traduzione del
codice
C++ in
assembler da parte di un compilatore, ho preferito mettere la struttura
fuori dalla classe in
una variabile globale allocata staticamente, in quanto in questa maniera
l'indirizzo dei vari membri e' gia' noto al
compilatore a compile-time e quindi viene usata una singola istruzione MOV
per prelevarne il
contenuto. Se invece nel nostro emulatore la struttura viene dichiarata come
variabile membro
della classe, il compilatore deve prima ottenere il puntatore this ai dati
dell'istanza, e
quindi prelevare il dato come prima, per un totale di due accessi."
Vedi? Se non sbaglio si tratta esattamente dell'esempio che ti ho scritto
prima.
Evidentemente nel codice di Luca non serviva avere la variabile (di non mi
ricordo quale tipo, forse una struct?) all'interno di un oggetto allora l'ha
creata come globale. Credo che avrebbe tranquillamente potuto lasciarla dove
era e semplicemente dichiararla 'static'. In questo ultimo caso non ci
sarebbe nessun bisogno di dereferenziare '*this' e calcolare poi lo
spiazzamento di 'var'.
Quindi si, è vero che per accedere una variabile all'interno di un oggetto
istanziato da classe o da struct (è uguale a questo proposito) sono
necessarie più operazioni rispetto ad accedere a variabili globali di tipo
nativo; però nel caso si voglia che l'oggetto abbia anche questo 'attributo'
non vedo altre soluzioni.
Saluti,
fabio de francesco
 
 
--------------------------------
Inviato via 
http://arianna.libero.it/usenet/

volte o devi essere parametrico.
No, io devo gestire fino a ben 3 Z80 contemporaneamente:
uno per lo Spectrum, uno per un'interfaccia audio chiamata Genera Sound
e un altro ancora per un'interfaccia disco chiamata FDD3000 (ho accennato
alla necessita' delle istanze multiple anche nel mio post precedente). Per
questo il compilatore non puo' ottimizzare, e come dici giustamente anche tu
deve essere per forza parametrico.
Ovviamente la struct globale contiene solo il contesto del processore 
emulato
di volta in volta. Da un'altra parte (dentro ogni istanza della classe) ho 
una copia
del contesto per ogni CPU (come un descrittore di processo in un sistema 
operativo)
che commuto switchandole avanti e indietro a seconda del processo. Questo
overhead e' per me trascurabile, in quanto queste commutazioni di contesto
sono eventi rarissimi a runtime: non piu' di 50 al secondo, e dovendo 
trasferire
ogni volta appena un centinaio scarso di bytes,  neanche me ne accorgo nei
miei benchmark. Invece, il guadagno che ottengo a lavorare in questo modo
rispetto che ad operare sulla struct variabile membro e' notevole,
come ho gia' spiegato.
Ciao,
Luca 

L'alternativa, magari piu' pulita ma probabilmente piu' laboriosa, e'
sperimentare con inlining e loop hoisting.
Ciao,
Nicola

 dell'efficienza estrema le cose possono cambiare.
Sono pienamente d'accordo!
Luca 

 volte o devi essere parametrico.
Ecco, questo già mi chiarisce molto la cosa, grazie.


 meno di crearla come appartenente alla classe.
Chiarissimo, grazie.


 [...]
Il discorso fila perfettamente, grazie. Ora credo proprio di essere in
sintonia.


 punto interessante da chiarire.
Non sempre. Una volta che un registro tiene il this, istruzioni
successive vicine che lavorano su altri dati membri della stessa classe
non hanno più bisogno di ricaricarselo dalla memoria. Questa cosa può
moltiplicarsi con l'uso di inline.
Ad esempio (cpu intel 386):
struct A {
        char c;
        int i;
        long l;
};
int main()
{
    A a;
    a.c = 11; // mov	BYTE PTR [%ebp-24], 11
    a.i = 22; // mov	DWORD PTR [%ebp-20], 22
    a.l = 33; // mov	DWORD PTR [%ebp-16], 33
    A* p = new A; // call	operator new(unsigned int)
    p->c = 44;    // mov	BYTE PTR [%eax], 44
    p->i = 55;    // mov	DWORD PTR [%eax+4], 55
    p->l = 66;    // mov	DWORD PTR [%eax+8], 66
    delete p;
}
(g++-3.4.4 -O1 -masm=intel, c++filt)
Secondo me, l'errore di fondo sta nel preoccuparsi di un fattore due che
potrebbe ridursi in base a come viene scritto il codice e a come viene
compilato, per non parlare poi di come verrà eseguito da una CPU con le
sue cache molto più grandi della RAM di uno Spectrum e le ottimizzazioni
dell'esecuzione delle istruzioni; ma questo è un'altro discorso e non
interessa all'argomento.
Io troverei più stimolante sviluppare un JIT alla Java per le istruzioni
della CPU simulata. I sorgenti del jdk sono in C++, scaricabili, e li ho
trovati ben scritti e documentati. Ci si potrebbe ispirare un po' da li.
Oppure, un pò più semplice il ccg qui:
http://www-sor.inria.fr/projects/vvm/realizations/ccg/ccg.html
dove mostrano qualcosa del tipo char buf[1024]; (( void(*)() )buf)(); 
(riempire array di char con codice macchina e farlo eseguire).
Inoltre, che ne so', usare i registri MMX per contenere il contesto di
una CPU Z80. Oltre a vedere il memory alignment come ho detto nell'altro
post, se sai che userai 3 CPU, allora fai un array globale di tre
elementi.
Saluti
-- 
int

"int i=0 0" <'@.'> ha scritto:
Scusa ma che significa l'istruzione:
char buf[1024];
((void(*)() )buf)();
Sembra che buf sia castato in un puntatore a funzione che non prende nessun
parametro e non restituisce alcun valore e che tale puntatore chiami la
funzione a cui punta...
Se lo eseguo mi da errore... forse servono istruzioni particolare
all'interno di buf (istruzioni macchina)? Oppure posso scrivere qualcosa
altro semplicemente per vedere cosa fa? Nel caso in cui servano istruzioni
macchina, puoi suggeririrne alcune di prova? Tralaltro le istruzioni devono
essere per forza lunghe un char per stare li o no?
Ciao

 essere per forza lunghe un char per stare li o no?
Si potrebbe riempire quell'array con random e lanciarlo. Poi, in base a
quello che succede, catalogarne e così via. Avremmo inventato un nuovo
modo di fare programmi, non molto dissimile da quelli in voga ...
ccg, vedi il link sull'altro posto, è fatto di varie macro per riempire
quel buf con istruzioni assembler di qualche CPU. La cosa interessante è
che, in fondo, puoi creare dinamicamente ed eseguire codice macchina e
mi pare misto a variabili C. In pratica, un assembler run-time.
La pratica di modificare a run-time il proprio codice viene sempre più
vietata dai sistemi operativi in genere, i segmenti contenenti codice
sono read-only, ma non si vieta di poter mandare l'instruction pointer
in un segmento dati, almeno finora in quanto mi pare di aver letto un
articolo dove forse verrà vietata anche questa cosa in certi casi.
In C++ è vietato da reinterpret_cast (g++: ISO C++ forbids casting
between pointer-to-function and pointer-to-object), ma permesso dal cast
stile C.
Saluti
-- 
int

 mi pare misto a variabili C. In pratica, un assembler run-time.
Ma c'e' solo per Linux questo ccg?
Ciao

 Ma c'e' solo per Linux questo ccg?
"The system is written entirely in ANSI C, and requires no
compiler-specific extensions (such as inline asm statements or the
various assembler-related extensions implemented by gcc)."
(
http://www-sor.inria.fr/projects/vvm/realizations/ccg/ccg.html
)
Saluti
-- 
int

 (
http://www-sor.inria.fr/projects/vvm/realizations/ccg/ccg.html
)
Si ma ho scaricato i sorgenti... e ho il makefile che se non sbaglio
permette la compilazione di tutto il pacchetto... ma non so come farlo
partire.
Ciao