SlideShare une entreprise Scribd logo
1  sur  80
Struttura Generale del Corso:  v0.5 Beta 1) Architettura dei Calcolatori: Generalità Processori e pipeline (ILP) Multiprocessori Architettura della memoria Sistemi di archiviazione 2) Basi di ingegneria del software: Il processo di creazione del software Modelizzazione a oggetti cenni di UML 3) Ambienti di sviluppo I compilatori gcc e icc in Linux L'ambiente Visual Studio (cenni di .net) 4) Introduzione alla programmazione a oggetti con il C++ ביאי
Architettura dei calcolatori; Tipi di calcolatori Definizione di Architettura Tendenze tecnologiche Principi di progettazione dei calcolatori Parallelismo, principio di localita' Legge di Amdahl Performance di un processore Errori comuni e miti. ביאי
 
Grazie all'ascesa dei microprocessori Dei vecchi tipi di calcolatori (minicomputer / vax) sono sopravvissuti solo I mainframe (IBM Z9). (Qual'e' il linguaggio piu' usato per I mainframe ?) Caratteristiche peso 2003 Kg potenza 18. KW memoria max 512 GB numero di processori dedicati 54 Generali 54 IFL ( dedicati a Linux) 16  ICF Interconnessione interna 27 zAAP Dedicati all'applicazione 27 zIIP Processori di informazione. Unita' Crittografica dedicata max 512 GB Progettato per un Up Time del 99.999%
I piu' moderni supercomputer sono realizzati con reti di microprocessori Ad esempio il CRAY attuali sono realizzati usando  reti di processori AMD. (Fino a 120000  cores). A lato una foto del computer che è stato piu' potente al mondo l'IBM blue Gene/L che raggiungge I 360 TeraFlop con una memoria di 32 tebibytes ovvero 32 2^40 Bytes. Il numero di nodi e' 65532 dual core,  la capacita' di scrittura su disco e' di  1024 Gbit/s su fs parallelo Recentemente anche Microsoft e' entrata tra I fornitori di soluzioni in ambito HPC con Windows Server 2008 HPC. (  http://www.microsoft.com/HPC/  )  --->{ Se volete crearvi un cluster virtuale è possibile } Non possiamo comunque abbandonare questo argomento senza citare il progetto APE dell'INFN.
Sia I mainframe che I supercomputer realizzati con reti di micropocessori (per una classifica si veda  www.top500.org ) offrono una grande affidabilita'. E in questi anni si sta anche affermando l'uso di macchine virtuali. La stessa che devono anche offrire molti SERVERS. Avete idea di quanto costa un'ora di “Downtime” per alcuni servizi ?
Nel progettare queste macchine l'affidabilita' 24/7 e' importante !
Il mercato Desktop e' il piu' vasto e lucroso. E' un ambiente ove conta molto il rapporto prezzo prestazioni specie per quanto riguarda la capacita' di calcolo e la grafica. Spesso I processori piu' nuovi (o a cui sia stato ridotto il prezzo) appaiono in questo mercato. Pensate alle esigenze di elaborazione sia grafica che di calcolo dei videogiochi ma anche di programmi professionali che usano intensivamente la CPU (CAD, risulutori FEM) La fascia di mercato e' molto vasta da poche centinaia di euro fino a qualche migliaio di euro per una singola mancchina.
Ma  vi e' una classe di calcolatori quasi invisibili per l'utente comune: I cosidetti “embedded computers” o calcolatori dedicati. Li troviamo in modo ubiquitario intorno a noi inclusi in ogni tipo di elettrodomestico ed anche all'interno delle automobili. Per molti di questi e' necessario operare in modalita' REAL TIME. Si dice che un programma opera in modo Real Time quando il tempo che ha a disposizione per terminare un determinato compito e' limitato. Si pensi per esempio al decodificatore satellitare di SKY. Il tempo che ha a disposizione per decodificare ciascun fotogramma e' limitato. Ma questo tipo di applicazione trova ampio uso in Fisica quando si disegnano I sistemi di acquisizione dati. I Sistemi di “Trigger” hanno un tempo limitato per decidere se acquisire l'evento oppure rigettarli. Poter operare in modalita' REAL TIME dipende anche dal sistema operativo. Per questo tipo di sistemi si passa da microcontrollori con memoria flash interna fino a processori di alta fascia.
Che cosa definisce una architettura di calcolatore ? Vi sono 3 aspetti fondamentali: Il Set di Istruzioni usato dal microprocessore. (ISA Instruction Set Architecture) L'organizzazione generale del progetto comprendendo tutti gli aspetti quali organizzazione della memoria, connessione della momoria, organizzazione interna del processore. Vi possono essere processori diversi che pur implementando lo stesso set di istruzioni hanno organizzazioni interne diverse. Hardware, ovvero la componentistica usata per l'effettiva realizzazione della macchina. Due processori anche molto simili fra loro con la stessa organizzazione e lo stesso ISA possono essere adatti a compiti diversi (applicazioni mobili, space qualified)
Che cosa caretterizza un set di istruzioni di un processore ? La sua  Classe  ( quasi tutti ora sono delle General Porpouse Register ISA ) Indirizzamento della memoria  quasi tutti I processori moderni indirizzano la memoria  come BYTE. Alcuni di questi richiedono che per indirizzare un oggetto entro la memoria questo debba essere  allineato  ovvero che se  s  e' la dimensione di un oggetto e  A  il suo indirizzo  A mod s = 0. L'80x86 non richede l'allignamento ma gli accessi allineati sono piu' veloci. Modi di indirizzamento – possono essere Assoluto, Relativo, a Registro ed altri  Tipo e grandezza degli operandi.  L'80x86 supporta interi di 8,16,32,64 bit e floting point (IEEE754) di 32 e 64 bit oltre alla precisione estesa di 80bit Operazioni. Processori come il MIPS hanno un set ridotto di istruzioni facile da mettere in pipeline e' un vero RISC, l'80x86 ne ha molte di piu'. Controllo di Flusso.  Tipi di salto condizionato e incondizionato, modi di ritorno Codifica  – Puo' essere a lunghezza fissa (MIPS)  o variabile (80x86 da 1 a 18 Byte)
Tendenze tecnologiche: La tecnologia ha fatto progressi enormi (ma questo non vuol dire che tutto cambi). Densita' dei transistors  aumenta di circa il 35% all'anno che combinata  con una crescita in del die di un10-20% comporta ad un aumento del 40-55% per anno  del numero transistor nel chip. La capacita' della memoria DRAM aumenta del 40% ogni anno. La capacita' dei dischi e' aumentata del 30% per anno fino al 1990.  Dal 1990 al 96 si e' avuto un aumento del 60% per anno che e' ulteriormente cresciuto al 100% per anno fino al 2004 per poi  riscendere sl 30%. Tutti questi cambiamenti obbligano I progettisti a “disegnare per il futuro”.
Le parole Bandwith o throughput definiscono il numero di operazioni eseguite in un certo tempo, mentre le parole latency o response time definiscono il tempo trscorso tra l'inizio e la fine di un singolo compito  La figura nella pagina seguente descrive I progressi effettuati sia in termini di larghezza di banda che di latenza. Se consideriamo un grande sistema vi sono due punti di vista: Il singolo utente e' interessato a diminuire la latency, mentre l'amministratore e' interessato ad aumentare la Bandiwith. Per confrontare due sistemi  X e Y riguado allo stesso task vale la formula: Ove per Time normalmente si intende wall clock time. Guardiamo ora la figura e cerchiamo di capire se ci puo' dire qualcosa riguardo a come dobbiamo disegnare I nostri programmi.
La bandwith migliora meglio che la latency......  questo ha a che fare qualcosa su come scriviamo I programmi ?
Tendenze Tecnologiche: “Feature Size” : Dimensioni della più piccola struttura (Transistor o filo ) su di un circuito integrato. Nel 1971  = 10 Micron Nel 2006  = 65 Nanometri (0.065 Micron) In produzione. Nel 2007 si sperimentano  a 45 nm La feature size  e' data dall'equazione di Rayleight: Ove  λ  e' la lunghezza d'onda con cui si illumina il wafer, K e' un fattore complesso ce contiene tutti I termini aggiuntivi dovuti per esempio alla qualita' del  photoresist  e all'uso di tecniche per il miglioramento della risoluzione (illuminazione fuori asse, griglie di spostamento di fase ed altre) e NA e' chiamata Numeriacl Aperture
 
Andamento della NA e del fattore K. Dal punto di vista pratico si considera  K > 0.25 e NA < 0.93. Quindi usando questi valori con una  λ  = 193 nm si ottiene una risoluzione limite di 52 nm. Ma si puo' andare oltre. Come si vede il limite fisico per la NA in aria e' 1. Ma le cose cambiano se usiamo un mezzo piu' denso.  In microscopia si usano lenti immerse in olio. Nel nostro caso si usa acqua ultrapura. INTEL HA GIA' MESSO IN PRODUZIONE CHIP DA 45nm
La tecnologia ha superato la legge di Moore: “ “ The complexity for minimum component cost has increased at a rate of roughly a factor of two per year”  Il numero di compunenti cresce quadraticamente rispetto alla feature size. La velocità cresce in modo più complesso a causa del ritardo del segnale nei fili all'interno del chip. Il Pentium 4 (2001) riserva due stadi della propria pipeline solo al ritardo dei segnali.
Consumo elettrico:  Un circuito CMOS ( Complementary MosFet ) consuma energia solo quando cambia stato. Questo è chiato consumo dinamico. I primi processori dissipavano  decimi di Watt. Un moderno Pentium 4 (3.5 GHz) ne dissipa circa 135.  Per abbassare la potenza assorbita si può abbassare la frequenza la tensione.  In 20 anni si è passati da 5V a circa 1V. Per un compito prefissato ridurre la frequenza riduce la potenza ma NON l'energia assorbita. Vi è una parte di potenza perduta tramite correnti parassite (25% circa nel 2006).
L'affidabilita' di un calcolatore  o di un sistema dipende da quella delle sue parti. Se in auto mi salta la cinghia devo farla riparare anche se tutte le altri meccaniche ed elettriche sono perfettamente funzionanti. Nei satelliti le parti critiche vengono ridondate e cosi' accade anche in alcuni computer con le ventole I dischi gli alimentatori ed anche I banchi di memoria per non parlare di interi sistemi ridondati (si veda ad esempio  L'unita' di misura usata e' il MTTF (Mean Time to Failure) spesso confuso col il MTBF (Mean Time Between Failures ) che e' invece  dato dalla somma del MTTF col MTTR (Mean Time to Repair).  L'inverso dell'MTTF e' chiamato failure rate o FIT di solito espresso per miliardo 10 9  di ore di funzionamento. Per ogni parte del sistema quindi si puo' definire:  Esercizio: Valutate il MTTF per un sistema che ha:  10 dischi con MTTF di  1000000  ore 1  controller SCSI   500000  ore 1 alimentatore   200000  ore 1  ventola   200000  ore 1 cavo SCSI 1000000  ore
Benchmarks. La scelta migliore e' usare applicazioni REALI. Ovvero I programmi che realmente vorreste usare sul vostro sistema. L'uso di programmi semplificati quali Kernels, toy programs, synthetic benchmarks provocano spesso errori nella reale valutazione delle prestazioni del sistema. Ricompilando un codice di una applicazione reale per per poter avere dei risultati significativamente confrontabili spesso bisogna usare un determinato compilatore con un ben determinato insieme di opzioni (vedremo che significa). Attualmente e' molto diffuso usare I benchmark della SPEC : (Standard Performance Evaluation Corporation)  http://www.spec.org   Attualmente la suite di applicazioni usate per provare le macchine e' arrivata alla sua quinta generazione SPEC2006 e contiene 12 programmi che operano su interi (CINT2006) e 17 programmi che operano in virgola mobile (CFP2006). Questi test vengono applicati a sistemi con singola CPU o (in piu' copie) su sistemi con CPU multiple. Altri benchmars sono dedicati  alle transazioni (  www.tpc.org  )  Come in un qualunque esperimento quando si riportano I risultati di un benchmark e' assolutamente necessario riportare le condizioni sperimentali nel del dettaglio.
Alcuni principi di progettazione dei calcolatori: Parallelismo: Puo' essere a tutti I livelli dal sistema al chip. Tipicamente nelle applicazioni WEB si aggiungono nuovi computer ad una farm per aumentare le prestazioni. Un sistema che puo' essere esteso come numero di processori o capacita' di dischi o altro e' chiamato scalabile. Quando parleremo di Pipeline parleremo proprio di parallelismo a livello di architettutra del processore (singolo core). Principio di Localita': Localita' temporale: 90% del tempo di elaborazione viene speso in 10% del codice. Localita' spaziale: gli accessi alla memoria tendono a riguardare locazioni vicine fra loro. Focalizzarsi sul caso piu' comune.
La legge di Amdhal E' importantissima e ci da' subito l'idea di quanto un codice potra' essere accelerato dopo una ottimizzazione. E' valida anche per interi sistemi e viene applicata anche quando si parallelizzano I codici per portarli su di un super computer. Il miglioramento delle performance di un programma (sistema) dipende: 1) Dalla frazione del programma (sistema) che puo' essere migliorata. 2) Da quanto e' il guadagno in performance di quella frazione. Chiamando Si ha:
Chiudiamo con alcune affermazioni FALSE: Il costo del processore e' il piu' importante. I Benchmark rimangono validi per sempre. Un disco con un MTTF di 1200000 ore non si guasta mai. Le prestazioni di picco sono un puon indice delle prestazioni reali.
Cominciamo ora a parlare della struttura interna di un processore moderno. Quello che diremo è solamente  una parte minima della materia  ma è importante che conosciate almeno le basi perché queste vi aiuteranno a scrivere I vostri programmi in modo più efficente sfruttando al meglio l'architettura del calcolatore che state usando. Per prima cosa dobbiamo accennare a che cosa sia un'ALU. La sigla sta per Arithmetic Logic Unit ed è una delle parti fondamentali del processore. La sua funzione è quella di svolgere TUTTE le funzioni aritmetico logliche fra due operandi. Un processore moderno può avere al suo interno più di un'ALU, ma non esiste (che io sappia) un processore che non ne abbia almeno una. Le ALU sono disponibili anche come componenti discreti.
A sinistra che riassume tutte le operazioni di un'ALU in basso  il diagramma a blocchi del componenete discreto. Da notare anche la presenza di registri interni. In tutti I processori (a me noti) ve ne sono. OpCode Registri Provate a cercare in internet in DataSheet di questo componente per avere maggiori dettagli.
[object Object]
Così come il montaggio di una automobile può essere diviso in tante operazioni che vengono svolte in parallelo, anche l' esecuzione di una istruzione di processore viene suddivisa in operazioni eseguite contemporaneamente.
Quello che vedremo è valido per un processore RISC: ,[object Object]
Gli unici accessi alla memoria sono di tipo LOAD e STORE
Il formato delle istruzioni è semplice e tipicamente di lunghezza fissa.
[object Object]
Decodifica e lettura dei registri: (ID) L'istruzione viene decodificata e gli operandi vengono letti dai registri  Nel caso di una istruzione di salto (BRANCH) viene calcolato l'indirizzo ove fare il salto e in alcune architetture viene eseguito. La decodifica dell'istruzione e lettura in parallelo sono possibili per il semplice formato delle istruzioni.
Esecuzione o calcolo dell'indirizzo effettivo: (EX) La ALU opera sui dati preparati nel ciclo precedente : Riferimento alla memoria: Somma base+offset per ottenere l'indirizzo effettivo Operazione fra registri: L'ALU esegue l'operazione indicata dall'OpCode Operazione registro-Dato: L'ALU esegue l'operazione indicata dall'OpCode
Accesso alla memoria:  (MEM) Se l'istruzione è un LOAD la memoria viene letta, se è uno STORE scritta alla locazione calcolata precedentemente.
Accesso ai registri: (WB) Nel caso di istruzioni di LOAD o istruzioni da fra registri dell'ALU il risultato dell'operazione viene scritto nel registro.
In questo tipo di implementzione le istruzioni di BRANCH vengono eseguite in due cicli, quelle di STORE in 4 e tutte le altre in 5. Supponendo che vi sia un 12% di BRANCH e un 10% di STORE calcolate quanti cicli in media richiede un'istruzione. Ciascuna istruzione viene eseguita più lentamentamente ma si un'accelerazione complessiva
Problemi nell'uso della Pipeline. Sono chimati  Hazards  e sono di tre tipi: 1) Di tipo strutturale:  Quando l'hardware non può gestire tutte le possibili combinazioni di istruzioni sovrapposte. 2)  Dovuti ai dati: a) Sono dovuti alla dipendenza di un'istruzione (  i  ) dal risultato di  una seguente (  j  ). - Tipo RAW (Read after Write)  j  tenta di leggere un dato prima  i   l'abbia scritto. - Tipo WAW (Write after Write)  j  scrive un risultato prima di  i  .  - Tipo WAR (Write after Read)  j  scrive un risultato prima che  i  lo legga. Nel tipo di pipeline statica che stiamo presentando (scrittura in un solo stadio e nessun riordino delle istruzioni) si ha solo il tipo RAW. b) (name dependency) all'uso di uno stesso registro di memoria senza però un reale passaggio di dati fra due istruzioni 3)  Dovuti al controllo di flusso: Sono dovuti alle istruzioni di BRANCH condizionato. Questo tipo di problemi obbliga a fermare la pipeline finchè il problema non si è risolto limitandone così le prestazioni.
Data Hazards: Considerate il seguente programma: Dadd  R1,R2,R3  // Somma  R2+R3 ----> R1 Dsub  R4,R1,R5  // Differenza  R1-R5  ----> R4 And  R6,R1,R7  // AND R1&R7 ----> R7 . . etc...... Tutte le istruzioni dopo la prima usano il suo risultato !  Ma mantre Dsub legge R1 nel suo stadio ID, Dadd lo scriverà solo durante il WB. Se non viene gestito correttamente il problema porta ad un risultato non deterministico del valore letto da Dsub. (Potrebbe esserci stato  un interrupt tra Dadd e Dsub) Questo tipo di problemi viene risolto nei processori moderni con una tecnica chiamata  forwarding .  Tralasciando I dettagli si tratta di scrivere il risultato di Dadd non solo in R1 (durante il WB) ma subito dove l'istruzione Dsub ne ha bisogno ovvero in entrata all'ALU. Ma non sempre questo trucco funziona.
L'accesso alle memoria MEM produce il suo risultato  tramite il forwarding  solo alla fine del ciclo di CLOCK mentre EX lo richiede all'inizio. Quindi è necessario fermare per un ciclo la pipe line Vi sono tecniche avanzate di ottimizzazione chiamte Dinamic scheduling o Out of Order execution ove le istruzioni sono eseguite in un ordine diverso da quello originale. Questo introduce nuovi possibili problemi di tipo WAR (WRITE after READ) e WAW  (Write after Write).
Control Hazards: Sono quei problemi dovuti a salti condizionati all'interno del programma. Vi sono diverse strategie. Una di queste scegliere un'istruzione da eseguire subito dopo quella di BRANCH in modo da prendere la decisione in seguito se fare o non fare il salto. Questa politica è chiamata “Branch delay slot”. Sta al compilatore scegliere che istruzione mettere. Questo è chiamato anche “static branch prediction”. Esitono e dobbiamo almeno citarle senza vederle in dettaglio tecniche di “dinamic branch prediction” ove, per  esempio , le decisioni prese per ciascun branch viengono registrate (branch prediction buffer) e la predizione viene  espressa in base alla storia passata. (profiling)
 
Per ottenere il massimo di prestazioni la pipeline deve essere costantemente tenuta piena. Per far questo bisogna sfruttare al massimo il parallelismo fra le istruzioni trovando nel programma sequenze di istruzioni non correlate fra loro che possono essere quindi sofrapposte all'interno della pipeline. Per evitare lo stallo due istruzioni dipendenti  i   e  j  devono essere separate fra loro almeno per un numero di clicli di clock tale da permettere a  i  di produrre un risultato utile. Consideriamo il seguente loop: For (i=1000; i>0; i=i-1) x[i]=x[i]+s ; Una traduzione letterale del codice in linguaggio macchina è assolutamente poco efficente. Non solo contiene un branch ma le dipendenze interne fra le istruzioni provocano una serie di stalli forzati della pipeline.
In fase di compilazione si può usare una tecnica chiamata LOOP UNROLLING, ovvero lo scrivere esplicitamente quali righe del programma le linne del ciclo fin quando il numero di registri disponibili lo permette. x[1]=x[1]+s ; x[2]=x[2]+s ; x[3]=x[3]+s ; . . . In questo modo si evitano I branch e le continue sottrazzioni  all'indice I e il codice diviene. Anche questo ha degli stalli ma I diversi LOAD e somme sono indipendenti e quindi..........
Posso riordinare il codice senza interferire sul suo risultato ottenendo: Questo ciclo non ha nessun stallo.  L'aver esplicitato I cicli del LOOP permette di aver più istruzioni e quindi  di poterle riordinare in modo più efficente. Una ottimizzazione del genere può sembrare banale ma non lo è. Richiede due tecniche di intelligenza artificiale: sostituzione simbolica e semplificazione. In questo le espressioni possono essere riscritte in modo da ridurre tutte le costanti ovvero: ((A+1)+1) viene riscritta come (A+(1+1)) e semplificata in (A+2) già a livello di compilazione.
Ma non tutto è sempre così semplice. Consideriamo il ciclo: For (i=1; i<=100; i++;) { A[i+1]=A[i]+C[i]; // S1 B[i+1]=B[i]+A[i+1]; // S2 } Qui abbiamo 2 tipi di dipendenza (S1) di cui una è detta  Loop Carried  ovvero dipendente dal ciclo. Inoltre si ha una dipendenza interna in ciascuna linea. In questo caso le iterzioni successive ad essere eseguite  in serie. Il secondo tipo di dipendenza (S2 che fa dipendere le due righe fra loro) non dipende dal ciclo quindi diverse istanze dello stesso ciclo potrebbero essere eseguite parallelamente. Non tutte le dipendenze Loop Carried impediscono il parallelismo.
For (i=1; i<=100; i++;) { A[i]=A[i]+B[i]; // S1 B[i+1]=C[i]+D[i]; // S2 } Consideriamo il ciclo: Vi è una dipendenza della prima riga sulla seconda attraverso il loop ma  la seconda riga non dipende dalla prima quindi le due righe potrebbero essere scambiate fra loro. Alla prima iterazione S1 dipende dal valore B[1] calcolato esternamente al loop. A[1]=A[1]+B[1]; For (i=1; i<=99; i++;) { B[i+1]=C[i]+D[i]; A[i+1]=A[i+1]+B[i+1];  } B[101]=C[100]+D[100]; Non vi è più nessuna dipendenza Loop Carried e il loop può essere eseguito in modo parallelo.
Quale ultimo caso vediamo quello di un'equazione ricorsiva: For (i=1000; i>0; i=i-1) x[i]=x[i]+x[i-1] ; Ovvero di una equazione ove una variabile è dipende da un suo valore definito in una iterazione precedente. Alcune architetture ( vettoriali ) sono particolarmente adatti ad eseguire calcoli ricorsivi e in alcuni casi vi è ancora spazio per il parallelismo: For (i=1000; i>5; i=i-1) x[i]=x[i]+ X[i-5] ; In questo caso si parla di una distastanza di dipendenza pari a 5. Più è lunga questa distanza più è possibile sfruttare il parallelismo tramite il loop unrolling.  Sperando di aver dato le basi..... qui ci fermiamo riguardo a questo argomento. Dovremo ancora parlare di Architetture Vettoriali, Gerarchia della Memoria e Software Engineering.....
Abbiamo fin qui visto (o per meglio dire accennato) a come sviluppare le capacità di calcolo di una CPU tramite l'ILP (Istruction Level Parallelism). Esistono limiti pratici a realizzare  CPU con pipelines sempre più profonde (maggiore complessità, registri, numero di transistor......) I processori vettoriali sono stati sviluppati già prima della implementazione dell'ILP. Una istruzione vettoriale è in grado di sostituire un intero ciclo. La parte di IF e ID sono più complesse ma la parte di esecuzione è estremamente più semplice. Non ci possono essere più dei “Control Hazards” dovuti al loop. Usando le istruzioni vettoriali tutte le dipendenze Loop Carried devono essere state risolte dal programmatore.  I “data hazards” vengono analizzate una sola volte per ogni vettore non per ogni elemento. Gli accessi alla memoria avvengono secondo schemi predefiniti riducendo la latenza e aumentando la larghezza di banda.  Non tutte le applicazioni possono trarre vantaggio da vettoriali ma molte di quelle scientifiche sì (e anche i videogiochi ).
Nel 2001 I calcolatori vettoriali sembravano sparire...... ma nel 2002 in giappone venne presentato l'Earth Simulator basato NEC SX-6. http://www.es.jamstec.go.jp/index.en.html Questa macchina ha 5120 processori vettoriali da 8GFlop ed è stata al primo  posto al mondo per potenza di calcolo fino al 2004 con una potenza totale di 35.86 Tflop. Ora è al 20 posto superata da IBM ma..... I nuovi processori vettoriali della macchina  SX9 annunciati da NEC hanno prestazioni che superano I 100 Gflop per una potenza totale (annunciata) di 839 Tflops. http://www.nec.co.jp/press/en/0710/2501.html
Architettura (di principio ) di un processore vettoriale.
Confrontando I due codici macchina la semplificazione risulta evidente.  Supponiamo di dover fare l'operazione vettoriale:  Y = Y +a* X
“Il processore G92 ha 112 cores con clock 1.5Ghz, il G80 ha 128 cores con clock a 1.3Ghz. Ogni core puo' effettuare una moltiplicazione e una addizione per clock. Peak(G92)=112*1.5*2=336GFlops, Peak(G80)=128*1.3*2=332GFlops C'e' poi altro hardware ( per calcolare funzioni come sin, cos, exp) piu' interpolatori per textures.” Le moderne shede grafiche hanno processori vettoriali con capacità di calcolo impressionanti.
 
La trasparenza precedente ci introduce all'Ingegneria del Software. Questo termine definisce l'applicazione di approccio, ordinato, quantificabile e sistematico allo  sviluppo, mantenimento, e messa in opera  del software. Venne introdotto nel 1968 nella NATO software conference.  Questa disciplina riguarda la raccolta delle specifiche, la progettazione, la costruzione, il test e il mantenimento dei programmi e raggruppa conoscenze da moltissimi campi. Il Software non è come un qualunque prodotto industriale: Si Sviluppa, Non si fabbrica. Ovvero Il software evolve costantemente nel tempo e quando questo non avviene “si deteriora”.
La parte di codifica ovvero la scrittura del software in un qualunque linguaggio è solo una piccola parte del proceso di sviluppo. La parte di progettazione che viene prima della codifica è di estrema importanza. Una volta prese alcune scelte architetturali queste NON potranno essere più cambiate in corso d'opera a meno di costi rilevanti.
In questo corso daremo solo alcuni principi base di ingegneria del software e una breve introduzione al linguaggio UML che come vedremo ci permetterà di modellizzare il software prima di srivererlo. Il primo principio che vorrei che aveste ben presente è quello di  documentare  il software che scrivete. Questo lavoro è assolutamente necessario. Ignorarlo porta alla costruzione di codici non mantenibili e potenzialmente pericolosi. In TUTTI gli esperimenti spaziali questo è assolutamente richiesto per poter volare. La documentazione deve iniziare iniziare sin dalle prime fasi di progettazione.
Vi sono molti MODELLI di SVILUPPO del software. I più comuni sono conosciuti come a  cascata , a  spirale e  a  V . Il modello a cascata è illustrato dal diagramma. Ha il limite di non essere flessibile specialmente se le richieste cambiano nel tempo I test sono separati dal progetto.
Richieste Analisi Progetto Codifica Test Accettazione Nel modello a spirale le diverse fasi vengono ripetute più volte passando attraverso la creazione di diversi prototipi che tenderanno sempre più verso il sistema finale. In TUTTI I modelli ciascuna fase viene documentata !
Il modello a V è molto usato anche nella progettazione di sistemi.
Per sistemi ad alta criticità quali quelli relativi alla Difesa, allo Spazio ma anche altri quali per esempio il software che deve gestire una metropolitana esistono METODI FORMALI, espressi matematicamente, che consentono di DIMOSTRARE la correttezza del software prodotto, ovvero il rispetto delle specifiche iniziali. La possibilità di usare questi metodi deriva dal fatto che un documento di specifica scritto in modo formale e privo di ambiguità contiene lo stesso quantitativo di informazione che il programma finale. Il codice prodotto quindi altro non è che una descrizione del problema che si vuole risolvere. Il più famoso fra questi metodi è stato prodotto da IBM ed è noto col nome di “CleanRoom”. Questo metodo di lavoro cerca di PREVENIRE gli errori piuttosto che correggerli integrando il test all'interno dello sviluppo
Già da molto tempo si sono rappresentati gli algoritmi tramite I cosiddetti “diagrammi di flusso” o flow chart. A Lato vedete un esempio (non formale....) di un diagramma di flusso di un programma che calcola il fattoriale.  (Notate che è una funzione ricorsiva). Similmente già da tempo esistevano programmi che permettevano di tradurre I diagrammi di flusso in codice sorgente.
Attualmente si usa il linguaggio UML ovvero l'UNIFIED MODELLING LANGUAGE non solo per rappresentare fra loro le classi di un programma C++ (a lato un esempio di relazioni fra classi) ma interi sistemi sia nei loro aspetti strutturali che nei loro aspetti di interazione ed evoluzione temporale.
A lato vediamo un esempio di diagramma UML  usato per rappresentare l'interazione di due attori (cliente e venditore) con un sistema di gestione degli ordini.
Nel moderno UML I vechi diagrammi di flusso possono essere visti come “Activity diagrams”
I diversi componenti fisici del sistema possono essere modellizzati con un Deployment Diagram.....
Ma si può modellizzare ben oltre un semplice programma C++ Una reale trattazione del linguaggio UML è oltre lo scopo di questo corso ma è importante che prendiate coscienza che questo tipo di strumento è presente nei moderni ambienti di lavoro ed è estremamente utile sia nella fase progettuale che di documentazione. Vi invito quindi a scaricare uno dei tanti editor UML gratuiti e a provare a documentare una vostra piccola applicazione tramite il linguaggio UML. Per esempio per Windows, MacOsX va molto bene Visual Paradigm nella community edition. Ne esistono anche diversi altri ma non sono tutti gratuiti ( Magicdraw ha una versione gratis). Per Linux potete usare Umbrello che viene distribuito con KDE o consultare questo articolo : http://www.linux.com/articles/47431  per avere una maggiore scelta.
1. template <class T> 2.  class data{ 3.  T d; 4.  T err; 5.  public: 6.  data() {d=(T)0.; err=(T)0.;} 7.  data(const T a, const T b){d=a; err=b;} 8.  data(const data &old){d=old.d; err=old.err;}  9.  ~data(){}; 10.  data& operator=(const data &old){d=old.d; err=old.err; return *this;} 11.  data& operator+=(const data &A){ 12.  data result; 13.  d=d+A.d; 14.  err=err+A.err; 15.  return *this; 16.  } 17.  const data operator+(const data &A){ 18.  data result; 19.  result.d=d+A.d; 20.  result.err=err+A.err; 21.  return result; 22.  } 23.  T  getd() {return d;} 24.  T  geterr() {return err;} 25.  void setd(const T a){d=a;} 26.  void seterr(const T a){err=a;} 27. };
1. template <class T> 2. class logbook{ 3. public: 4.  logbook(){n=0;} // Il creatore vuoto non deve fare nulla. 5.  logbook(const class data<T> A){n=0; lemisure.push_back(A); n++;} // Il creatore con un argomento inserisce il primo elemento 6.  logbook(const  logbook &old){ 7.  int i; 8.  n=old.n; 9.  lemisure.erase(lemisure.begin(),lemisure.end()); //svuoto il vettore di partenza 10.  for(i=0;i<n;i++){lemisure.push_back(old.lemisure[i]);} //e lo riempio coi nuovi dati 11.  } 12.  ~logbook(){}; 13. /* 14. Di nuovo ridefinisco l'operatore = 15. */ 16.  logbook& operator=(const logbook &old){ 17.  int i; 18.  n=old.n; 19.  lemisure.erase(lemisure.begin(),lemisure.end()); 20.  for(i=0;i<n;i++){lemisure.push_back(old.lemisure[i]);} 21.  return *this;} 22.  int  getn(){return n;}; 23.  void addm(const class data<T> A){ 24.  lemisure.push_back(A); 25.  n++; 26.  } 27. /* 28. Un esempio (molto banale) di funzione che opera sui dati. */ 29. T mean(){int i; 30. T media; 31. media=0; 32. for(i=0;i<n;i++){media=media+lemisure[i].getd();} return media /( T ) n; 33. } 34. private: 35.  vector<class data<T> > lemisure; 36.  int n; 37. };
Cache memory: è una memoria veloce e di piccole dimensioni fra il processore e la RAM. Un uso corretto della cache (i.e. accedere a informazioni vicine fra loro) può velocizzare in modo significativo i vostri programmi !
#include <stdlib.h> #include  <stdio.h> #include <time.h> #define Nu  10000 int  main() { clock_t ticks1, ticks2; int  i,j; double  *A; A = ( double  *)malloc(Nu*Nu* sizeof ( double )); ticks1=clock();  // inizializza il timer for (i= 0 ;i<Nu;i++){ for (j= 0 ;j<Nu;j++){ *(A+(j*Nu+i))=i+j; } } ticks2=clock();  // secondo timer  printf( &quot; Tempo in millisecondi : %ld  &quot; ,(ticks2-ticks1)* 1000 /CLOCKS_PER_SEC); } Provate questo codice. La Cache è usata bene ? Si può renderlo più performante ?
for  (i= 0 ; i<Ndim; i++){ for  (j= 0 ; j<Mdim; j++){ tmp =  0.0 ; for (k= 0 ;k<Pdim;k++){ /* C(i,j) = sum(over k) A(i,k) * B(k,j) */ tmp += *(A+(i*Ndim+k)) *  *(B+(k*Pdim+j)); } *(C+(i*Ndim+j)) = tmp; } } Similmente questo frammento di codice esegue una moltiplicazione di matrici. Supponiamo di avere matrici piuttosto grandi (e.g. double 1000x1000 ) La Cache è usata bene ?  Come potrei migliorare questo codice ?
Accenniamo ora, almeno a livello introduttivo ad alcuni concetti riguardanti la programmazione parallela.  Questo tipo di argomenti diverrà sempre più importante con la diffusione dei processori multicore e delle macchine multiprocessore. Abbiamo già accennato a questi argomenti quando abbiamo parlato delle schede grafiche (GPU) usate per calcolo scientifico. In quel caso avevamo già introdotto la parola “Thread”, e se vi ricordate ne abbiamo anche parlato quando abiamo visto gli “Activity Diagrams” dell'UML, senza però darne una definizione precisa. Il materiale in questa lezione è ricavato in gran parte da un seminario WEB di INTEL che sta facendo un grande sforzo per rieducare I programmatori ad usare metodi paralleli.
“ Istituzioni di Sistemi Operativi”. Si chiama PROCESSO una unità di lavoro, gestita dal sistema opertivo che ha un proprio spazio di memoria (address space(heap)) e le risorse che gli vengono messe a disposizione dal sistema operativo. Si chiama THREAD una sotto unità di lavoro all'interno di un processo. Un thread ha un proprio program counter e stack ma condivide tutte le altre risorse col processo che lo ha generato. I Thread sono le unità naturali di lavoro per gli ambienti con memoria condivisa (noti anche come UMA Uniform Memory Access). Dato che condividono le risorse NON dobiamo dividere I dati del nostro problema in più parti.
Un esempio (da voi ben conosciuto !) di problema parallelizzabile:
Il programmino per calcolare Pigreco:  [ CONTIENE UN ERRORE ! Dove ? ] #include <iostream> using namespace std; static long num_steps = 1000000; double step; void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); } pi = step * sum; cout << &quot; Ed ecco a voi Pi: &quot; << pi<< endl; cin >> i; return 0; }
In questa lezione vedremo solo due esempi su come parallelizzare questo programma: L'uso delle direttive di compilazione OpenMP, e le librerie MPI (Message passing Interface) Non trattiamo le librerie per gestire direttamente I thread da sistema operativo (Win32 o Pthreads) perchè un pò' più complesse. Nella pagina web del corso trovate i link per approfondire l'argomento. Open MP è una serie di direttive per il compilatore e librerie per costruire applicazioni parallele. Rendono facile creare processi con più thread partendo da programmi Fortran C e C++ esistenti. Sono diventate uno standard negli ultimi 15 anni.
Il modello di programmazione in OpenMP è chiamato “Fork-Join”: Un Thread principali lancia gruppi di thread paralleli, Il parallelismo può essere introdotto in modo increamentale nel programma esistente facendo evolvere le parti sequenziali in codice parallelo (lì dove è possibile). Thread principale Zone Parallele Zone Scalari
Non dovete spaventarvi quando parlo di “direttive del compilatore”. In realtà le state già usanddo da tempo. Quando scrivete #INCLUDE <IOSTREAM> avete scritto qualcosa che NON è nel linguaggio C++ ma è una istruzione al compilatore perchè includa quelle librerie nel vostro programma. Quasi tutto il lavoro di OpenMP viene fatto tramite direttive date la compilatore.  Sono poche le funzioni da aggiungere al codice. Per esempio con: #pragma omp parallel { ///Tutti I thread eseguono le istruzioni in questo blocco. } Oppure  #pragma omp parallel for for(i=0;i<N;i++) { /// Ciascun thread riceve parte dei loop } Vi  sono anche direttive per modificare il comportamento rispetto alle variabili: Private (lista....)  crea una copia privata delle variabili per ciascun thread, Reduction(Op: lista)  esegue una operazione di riduzione locale tramite l'operatore OP della lista di variabili.
#include  <iostream> #include  <string> #include  <stdio.h> #include  <omp.h> using   namespace  std; static   long  num_steps =  1000000 ; double  step; #define NUM_THREADS  2 int  main () {  int  i;  double  x, pi, sum =  0.0 ; step =  1.0 /( double ) num_steps; omp_set_num_threads(NUM_THREADS); #pragma omp parallel for reduction(+:sum) private(x) for  (i= 1 ;i<= num_steps; i++){ x = (i- 0.5 )*step; if  (sum== 0 ) printf( &quot;Sono nel ciclo For parallelo  : %d  %d&quot; ,  omp_get_num_threads(), omp_get_thread_num()); sum = sum +  4.0 /( 1.0 +x*x);  } pi = step * sum; cout <<  &quot;Ed ecco a voi:  &quot;  << pi <<  &quot; Numero di trhead:&quot; << omp_get_num_threads() << endl; return   0 ; } Notate l'operazione di riduzione sulla variabile sum e il fatto che la variabile x sia tenuta privata. Domanda: Perché ho usato printf invece che cout nel ciclo ?

Contenu connexe

Similaire à Lezioni 2009

Il web service e i sistemi embedded - Tesi - cap2
Il web service e i sistemi embedded - Tesi - cap2Il web service e i sistemi embedded - Tesi - cap2
Il web service e i sistemi embedded - Tesi - cap2pma77
 
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacy
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacyProgettazione di uno strumento per la reingegnerizzazione di applicazioni legacy
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacyGiacomo Russo
 
Fpga il componente universale 2010-11-09
Fpga  il componente universale   2010-11-09Fpga  il componente universale   2010-11-09
Fpga il componente universale 2010-11-09Ionela
 
Industrial iot: dalle parole ai fatti
Industrial iot: dalle parole ai fatti Industrial iot: dalle parole ai fatti
Industrial iot: dalle parole ai fatti Riccardo Zamana
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1guest5c2d35b
 
Presentazione Nuvola Vertica Full
Presentazione Nuvola Vertica FullPresentazione Nuvola Vertica Full
Presentazione Nuvola Vertica FullAlberto.F
 
Presentazione Nuvola Vertica F
Presentazione Nuvola Vertica FPresentazione Nuvola Vertica F
Presentazione Nuvola Vertica FAlberto.F
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Alberto.F
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Alberto.F
 
Presentazione Nuvola Vertica Full New
Presentazione Nuvola Vertica Full NewPresentazione Nuvola Vertica Full New
Presentazione Nuvola Vertica Full NewAlberto.F
 
Software libero nei sistemi embedded
Software libero nei sistemi embeddedSoftware libero nei sistemi embedded
Software libero nei sistemi embeddedDaniele Costarella
 
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di Brocade
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di BrocadeModernizzare i data Center con le soluzioni Fibre Channel Gen 6 di Brocade
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di BrocadeAntonio Palumbo
 
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...Giuseppe Luciano
 
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...Giacomo Russo
 

Similaire à Lezioni 2009 (20)

Assemblare un pc
Assemblare un pcAssemblare un pc
Assemblare un pc
 
Il web service e i sistemi embedded - Tesi - cap2
Il web service e i sistemi embedded - Tesi - cap2Il web service e i sistemi embedded - Tesi - cap2
Il web service e i sistemi embedded - Tesi - cap2
 
cècè
 
Evoluzionecomputer
EvoluzionecomputerEvoluzionecomputer
Evoluzionecomputer
 
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacy
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacyProgettazione di uno strumento per la reingegnerizzazione di applicazioni legacy
Progettazione di uno strumento per la reingegnerizzazione di applicazioni legacy
 
Fpga il componente universale 2010-11-09
Fpga  il componente universale   2010-11-09Fpga  il componente universale   2010-11-09
Fpga il componente universale 2010-11-09
 
IBM FlashSystem 820 e 720
IBM FlashSystem 820 e 720IBM FlashSystem 820 e 720
IBM FlashSystem 820 e 720
 
Cesvip 20110127
Cesvip 20110127Cesvip 20110127
Cesvip 20110127
 
Industrial iot: dalle parole ai fatti
Industrial iot: dalle parole ai fatti Industrial iot: dalle parole ai fatti
Industrial iot: dalle parole ai fatti
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1
 
Presentazione Nuvola Vertica Full
Presentazione Nuvola Vertica FullPresentazione Nuvola Vertica Full
Presentazione Nuvola Vertica Full
 
Presentazione Nuvola Vertica F
Presentazione Nuvola Vertica FPresentazione Nuvola Vertica F
Presentazione Nuvola Vertica F
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1
 
Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1Presentazione Nuvola Vertica Full New1
Presentazione Nuvola Vertica Full New1
 
Presentazione Nuvola Vertica Full New
Presentazione Nuvola Vertica Full NewPresentazione Nuvola Vertica Full New
Presentazione Nuvola Vertica Full New
 
Software libero nei sistemi embedded
Software libero nei sistemi embeddedSoftware libero nei sistemi embedded
Software libero nei sistemi embedded
 
Tesi
TesiTesi
Tesi
 
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di Brocade
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di BrocadeModernizzare i data Center con le soluzioni Fibre Channel Gen 6 di Brocade
Modernizzare i data Center con le soluzioni Fibre Channel Gen 6 di Brocade
 
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...
Soluzioni distribuite per l’analisi di dati biomedici in ambiente Virtual Dat...
 
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...
Progetto MigrOS: progettazione e sviluppo degli strumenti di transcodifica de...
 

Dernier

Corso di digitalizzazione e reti per segretario amministrativo
Corso di digitalizzazione e reti per segretario amministrativoCorso di digitalizzazione e reti per segretario amministrativo
Corso di digitalizzazione e reti per segretario amministrativovaleriodinoia35
 
lezione di fisica_I moti nel piano_Amaldi
lezione di fisica_I moti nel piano_Amaldilezione di fisica_I moti nel piano_Amaldi
lezione di fisica_I moti nel piano_Amaldivaleriodinoia35
 
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia Romana
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia RomanaXI Lezione - Arabo LAR Giath Rammo @ Libera Accademia Romana
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia RomanaStefano Lariccia
 
XIII Lezione - Arabo G.Rammo @ Libera Accademia Romana
XIII Lezione - Arabo G.Rammo @ Libera Accademia RomanaXIII Lezione - Arabo G.Rammo @ Libera Accademia Romana
XIII Lezione - Arabo G.Rammo @ Libera Accademia RomanaStefano Lariccia
 
La seconda guerra mondiale per licei e scuole medie
La seconda guerra mondiale per licei e scuole medieLa seconda guerra mondiale per licei e scuole medie
La seconda guerra mondiale per licei e scuole medieVincenzoPantalena1
 
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla Cresima
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla CresimaIL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla Cresima
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla CresimaRafael Figueredo
 
Ticonzero news 148.pdf aprile 2024 Terza cultura
Ticonzero news 148.pdf aprile 2024 Terza culturaTiconzero news 148.pdf aprile 2024 Terza cultura
Ticonzero news 148.pdf aprile 2024 Terza culturaPierLuigi Albini
 
Esperimenti_laboratorio di fisica per la scuola superiore
Esperimenti_laboratorio di fisica per la scuola superioreEsperimenti_laboratorio di fisica per la scuola superiore
Esperimenti_laboratorio di fisica per la scuola superiorevaleriodinoia35
 

Dernier (8)

Corso di digitalizzazione e reti per segretario amministrativo
Corso di digitalizzazione e reti per segretario amministrativoCorso di digitalizzazione e reti per segretario amministrativo
Corso di digitalizzazione e reti per segretario amministrativo
 
lezione di fisica_I moti nel piano_Amaldi
lezione di fisica_I moti nel piano_Amaldilezione di fisica_I moti nel piano_Amaldi
lezione di fisica_I moti nel piano_Amaldi
 
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia Romana
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia RomanaXI Lezione - Arabo LAR Giath Rammo @ Libera Accademia Romana
XI Lezione - Arabo LAR Giath Rammo @ Libera Accademia Romana
 
XIII Lezione - Arabo G.Rammo @ Libera Accademia Romana
XIII Lezione - Arabo G.Rammo @ Libera Accademia RomanaXIII Lezione - Arabo G.Rammo @ Libera Accademia Romana
XIII Lezione - Arabo G.Rammo @ Libera Accademia Romana
 
La seconda guerra mondiale per licei e scuole medie
La seconda guerra mondiale per licei e scuole medieLa seconda guerra mondiale per licei e scuole medie
La seconda guerra mondiale per licei e scuole medie
 
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla Cresima
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla CresimaIL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla Cresima
IL CHIAMATO ALLA CONVERSIONE - catechesi per candidati alla Cresima
 
Ticonzero news 148.pdf aprile 2024 Terza cultura
Ticonzero news 148.pdf aprile 2024 Terza culturaTiconzero news 148.pdf aprile 2024 Terza cultura
Ticonzero news 148.pdf aprile 2024 Terza cultura
 
Esperimenti_laboratorio di fisica per la scuola superiore
Esperimenti_laboratorio di fisica per la scuola superioreEsperimenti_laboratorio di fisica per la scuola superiore
Esperimenti_laboratorio di fisica per la scuola superiore
 

Lezioni 2009

  • 1. Struttura Generale del Corso: v0.5 Beta 1) Architettura dei Calcolatori: Generalità Processori e pipeline (ILP) Multiprocessori Architettura della memoria Sistemi di archiviazione 2) Basi di ingegneria del software: Il processo di creazione del software Modelizzazione a oggetti cenni di UML 3) Ambienti di sviluppo I compilatori gcc e icc in Linux L'ambiente Visual Studio (cenni di .net) 4) Introduzione alla programmazione a oggetti con il C++ ביאי
  • 2. Architettura dei calcolatori; Tipi di calcolatori Definizione di Architettura Tendenze tecnologiche Principi di progettazione dei calcolatori Parallelismo, principio di localita' Legge di Amdahl Performance di un processore Errori comuni e miti. ביאי
  • 3.  
  • 4. Grazie all'ascesa dei microprocessori Dei vecchi tipi di calcolatori (minicomputer / vax) sono sopravvissuti solo I mainframe (IBM Z9). (Qual'e' il linguaggio piu' usato per I mainframe ?) Caratteristiche peso 2003 Kg potenza 18. KW memoria max 512 GB numero di processori dedicati 54 Generali 54 IFL ( dedicati a Linux) 16 ICF Interconnessione interna 27 zAAP Dedicati all'applicazione 27 zIIP Processori di informazione. Unita' Crittografica dedicata max 512 GB Progettato per un Up Time del 99.999%
  • 5. I piu' moderni supercomputer sono realizzati con reti di microprocessori Ad esempio il CRAY attuali sono realizzati usando reti di processori AMD. (Fino a 120000 cores). A lato una foto del computer che è stato piu' potente al mondo l'IBM blue Gene/L che raggiungge I 360 TeraFlop con una memoria di 32 tebibytes ovvero 32 2^40 Bytes. Il numero di nodi e' 65532 dual core, la capacita' di scrittura su disco e' di 1024 Gbit/s su fs parallelo Recentemente anche Microsoft e' entrata tra I fornitori di soluzioni in ambito HPC con Windows Server 2008 HPC. ( http://www.microsoft.com/HPC/ ) --->{ Se volete crearvi un cluster virtuale è possibile } Non possiamo comunque abbandonare questo argomento senza citare il progetto APE dell'INFN.
  • 6. Sia I mainframe che I supercomputer realizzati con reti di micropocessori (per una classifica si veda www.top500.org ) offrono una grande affidabilita'. E in questi anni si sta anche affermando l'uso di macchine virtuali. La stessa che devono anche offrire molti SERVERS. Avete idea di quanto costa un'ora di “Downtime” per alcuni servizi ?
  • 7. Nel progettare queste macchine l'affidabilita' 24/7 e' importante !
  • 8. Il mercato Desktop e' il piu' vasto e lucroso. E' un ambiente ove conta molto il rapporto prezzo prestazioni specie per quanto riguarda la capacita' di calcolo e la grafica. Spesso I processori piu' nuovi (o a cui sia stato ridotto il prezzo) appaiono in questo mercato. Pensate alle esigenze di elaborazione sia grafica che di calcolo dei videogiochi ma anche di programmi professionali che usano intensivamente la CPU (CAD, risulutori FEM) La fascia di mercato e' molto vasta da poche centinaia di euro fino a qualche migliaio di euro per una singola mancchina.
  • 9. Ma vi e' una classe di calcolatori quasi invisibili per l'utente comune: I cosidetti “embedded computers” o calcolatori dedicati. Li troviamo in modo ubiquitario intorno a noi inclusi in ogni tipo di elettrodomestico ed anche all'interno delle automobili. Per molti di questi e' necessario operare in modalita' REAL TIME. Si dice che un programma opera in modo Real Time quando il tempo che ha a disposizione per terminare un determinato compito e' limitato. Si pensi per esempio al decodificatore satellitare di SKY. Il tempo che ha a disposizione per decodificare ciascun fotogramma e' limitato. Ma questo tipo di applicazione trova ampio uso in Fisica quando si disegnano I sistemi di acquisizione dati. I Sistemi di “Trigger” hanno un tempo limitato per decidere se acquisire l'evento oppure rigettarli. Poter operare in modalita' REAL TIME dipende anche dal sistema operativo. Per questo tipo di sistemi si passa da microcontrollori con memoria flash interna fino a processori di alta fascia.
  • 10. Che cosa definisce una architettura di calcolatore ? Vi sono 3 aspetti fondamentali: Il Set di Istruzioni usato dal microprocessore. (ISA Instruction Set Architecture) L'organizzazione generale del progetto comprendendo tutti gli aspetti quali organizzazione della memoria, connessione della momoria, organizzazione interna del processore. Vi possono essere processori diversi che pur implementando lo stesso set di istruzioni hanno organizzazioni interne diverse. Hardware, ovvero la componentistica usata per l'effettiva realizzazione della macchina. Due processori anche molto simili fra loro con la stessa organizzazione e lo stesso ISA possono essere adatti a compiti diversi (applicazioni mobili, space qualified)
  • 11. Che cosa caretterizza un set di istruzioni di un processore ? La sua Classe ( quasi tutti ora sono delle General Porpouse Register ISA ) Indirizzamento della memoria quasi tutti I processori moderni indirizzano la memoria come BYTE. Alcuni di questi richiedono che per indirizzare un oggetto entro la memoria questo debba essere allineato ovvero che se s e' la dimensione di un oggetto e A il suo indirizzo A mod s = 0. L'80x86 non richede l'allignamento ma gli accessi allineati sono piu' veloci. Modi di indirizzamento – possono essere Assoluto, Relativo, a Registro ed altri Tipo e grandezza degli operandi. L'80x86 supporta interi di 8,16,32,64 bit e floting point (IEEE754) di 32 e 64 bit oltre alla precisione estesa di 80bit Operazioni. Processori come il MIPS hanno un set ridotto di istruzioni facile da mettere in pipeline e' un vero RISC, l'80x86 ne ha molte di piu'. Controllo di Flusso. Tipi di salto condizionato e incondizionato, modi di ritorno Codifica – Puo' essere a lunghezza fissa (MIPS) o variabile (80x86 da 1 a 18 Byte)
  • 12. Tendenze tecnologiche: La tecnologia ha fatto progressi enormi (ma questo non vuol dire che tutto cambi). Densita' dei transistors aumenta di circa il 35% all'anno che combinata con una crescita in del die di un10-20% comporta ad un aumento del 40-55% per anno del numero transistor nel chip. La capacita' della memoria DRAM aumenta del 40% ogni anno. La capacita' dei dischi e' aumentata del 30% per anno fino al 1990. Dal 1990 al 96 si e' avuto un aumento del 60% per anno che e' ulteriormente cresciuto al 100% per anno fino al 2004 per poi riscendere sl 30%. Tutti questi cambiamenti obbligano I progettisti a “disegnare per il futuro”.
  • 13. Le parole Bandwith o throughput definiscono il numero di operazioni eseguite in un certo tempo, mentre le parole latency o response time definiscono il tempo trscorso tra l'inizio e la fine di un singolo compito La figura nella pagina seguente descrive I progressi effettuati sia in termini di larghezza di banda che di latenza. Se consideriamo un grande sistema vi sono due punti di vista: Il singolo utente e' interessato a diminuire la latency, mentre l'amministratore e' interessato ad aumentare la Bandiwith. Per confrontare due sistemi X e Y riguado allo stesso task vale la formula: Ove per Time normalmente si intende wall clock time. Guardiamo ora la figura e cerchiamo di capire se ci puo' dire qualcosa riguardo a come dobbiamo disegnare I nostri programmi.
  • 14. La bandwith migliora meglio che la latency...... questo ha a che fare qualcosa su come scriviamo I programmi ?
  • 15. Tendenze Tecnologiche: “Feature Size” : Dimensioni della più piccola struttura (Transistor o filo ) su di un circuito integrato. Nel 1971 = 10 Micron Nel 2006 = 65 Nanometri (0.065 Micron) In produzione. Nel 2007 si sperimentano a 45 nm La feature size e' data dall'equazione di Rayleight: Ove λ e' la lunghezza d'onda con cui si illumina il wafer, K e' un fattore complesso ce contiene tutti I termini aggiuntivi dovuti per esempio alla qualita' del photoresist e all'uso di tecniche per il miglioramento della risoluzione (illuminazione fuori asse, griglie di spostamento di fase ed altre) e NA e' chiamata Numeriacl Aperture
  • 16.  
  • 17. Andamento della NA e del fattore K. Dal punto di vista pratico si considera K > 0.25 e NA < 0.93. Quindi usando questi valori con una λ = 193 nm si ottiene una risoluzione limite di 52 nm. Ma si puo' andare oltre. Come si vede il limite fisico per la NA in aria e' 1. Ma le cose cambiano se usiamo un mezzo piu' denso. In microscopia si usano lenti immerse in olio. Nel nostro caso si usa acqua ultrapura. INTEL HA GIA' MESSO IN PRODUZIONE CHIP DA 45nm
  • 18. La tecnologia ha superato la legge di Moore: “ “ The complexity for minimum component cost has increased at a rate of roughly a factor of two per year” Il numero di compunenti cresce quadraticamente rispetto alla feature size. La velocità cresce in modo più complesso a causa del ritardo del segnale nei fili all'interno del chip. Il Pentium 4 (2001) riserva due stadi della propria pipeline solo al ritardo dei segnali.
  • 19. Consumo elettrico: Un circuito CMOS ( Complementary MosFet ) consuma energia solo quando cambia stato. Questo è chiato consumo dinamico. I primi processori dissipavano decimi di Watt. Un moderno Pentium 4 (3.5 GHz) ne dissipa circa 135. Per abbassare la potenza assorbita si può abbassare la frequenza la tensione. In 20 anni si è passati da 5V a circa 1V. Per un compito prefissato ridurre la frequenza riduce la potenza ma NON l'energia assorbita. Vi è una parte di potenza perduta tramite correnti parassite (25% circa nel 2006).
  • 20. L'affidabilita' di un calcolatore o di un sistema dipende da quella delle sue parti. Se in auto mi salta la cinghia devo farla riparare anche se tutte le altri meccaniche ed elettriche sono perfettamente funzionanti. Nei satelliti le parti critiche vengono ridondate e cosi' accade anche in alcuni computer con le ventole I dischi gli alimentatori ed anche I banchi di memoria per non parlare di interi sistemi ridondati (si veda ad esempio L'unita' di misura usata e' il MTTF (Mean Time to Failure) spesso confuso col il MTBF (Mean Time Between Failures ) che e' invece dato dalla somma del MTTF col MTTR (Mean Time to Repair). L'inverso dell'MTTF e' chiamato failure rate o FIT di solito espresso per miliardo 10 9 di ore di funzionamento. Per ogni parte del sistema quindi si puo' definire: Esercizio: Valutate il MTTF per un sistema che ha: 10 dischi con MTTF di 1000000 ore 1 controller SCSI 500000 ore 1 alimentatore 200000 ore 1 ventola 200000 ore 1 cavo SCSI 1000000 ore
  • 21. Benchmarks. La scelta migliore e' usare applicazioni REALI. Ovvero I programmi che realmente vorreste usare sul vostro sistema. L'uso di programmi semplificati quali Kernels, toy programs, synthetic benchmarks provocano spesso errori nella reale valutazione delle prestazioni del sistema. Ricompilando un codice di una applicazione reale per per poter avere dei risultati significativamente confrontabili spesso bisogna usare un determinato compilatore con un ben determinato insieme di opzioni (vedremo che significa). Attualmente e' molto diffuso usare I benchmark della SPEC : (Standard Performance Evaluation Corporation) http://www.spec.org Attualmente la suite di applicazioni usate per provare le macchine e' arrivata alla sua quinta generazione SPEC2006 e contiene 12 programmi che operano su interi (CINT2006) e 17 programmi che operano in virgola mobile (CFP2006). Questi test vengono applicati a sistemi con singola CPU o (in piu' copie) su sistemi con CPU multiple. Altri benchmars sono dedicati alle transazioni ( www.tpc.org ) Come in un qualunque esperimento quando si riportano I risultati di un benchmark e' assolutamente necessario riportare le condizioni sperimentali nel del dettaglio.
  • 22. Alcuni principi di progettazione dei calcolatori: Parallelismo: Puo' essere a tutti I livelli dal sistema al chip. Tipicamente nelle applicazioni WEB si aggiungono nuovi computer ad una farm per aumentare le prestazioni. Un sistema che puo' essere esteso come numero di processori o capacita' di dischi o altro e' chiamato scalabile. Quando parleremo di Pipeline parleremo proprio di parallelismo a livello di architettutra del processore (singolo core). Principio di Localita': Localita' temporale: 90% del tempo di elaborazione viene speso in 10% del codice. Localita' spaziale: gli accessi alla memoria tendono a riguardare locazioni vicine fra loro. Focalizzarsi sul caso piu' comune.
  • 23. La legge di Amdhal E' importantissima e ci da' subito l'idea di quanto un codice potra' essere accelerato dopo una ottimizzazione. E' valida anche per interi sistemi e viene applicata anche quando si parallelizzano I codici per portarli su di un super computer. Il miglioramento delle performance di un programma (sistema) dipende: 1) Dalla frazione del programma (sistema) che puo' essere migliorata. 2) Da quanto e' il guadagno in performance di quella frazione. Chiamando Si ha:
  • 24. Chiudiamo con alcune affermazioni FALSE: Il costo del processore e' il piu' importante. I Benchmark rimangono validi per sempre. Un disco con un MTTF di 1200000 ore non si guasta mai. Le prestazioni di picco sono un puon indice delle prestazioni reali.
  • 25. Cominciamo ora a parlare della struttura interna di un processore moderno. Quello che diremo è solamente una parte minima della materia ma è importante che conosciate almeno le basi perché queste vi aiuteranno a scrivere I vostri programmi in modo più efficente sfruttando al meglio l'architettura del calcolatore che state usando. Per prima cosa dobbiamo accennare a che cosa sia un'ALU. La sigla sta per Arithmetic Logic Unit ed è una delle parti fondamentali del processore. La sua funzione è quella di svolgere TUTTE le funzioni aritmetico logliche fra due operandi. Un processore moderno può avere al suo interno più di un'ALU, ma non esiste (che io sappia) un processore che non ne abbia almeno una. Le ALU sono disponibili anche come componenti discreti.
  • 26. A sinistra che riassume tutte le operazioni di un'ALU in basso il diagramma a blocchi del componenete discreto. Da notare anche la presenza di registri interni. In tutti I processori (a me noti) ve ne sono. OpCode Registri Provate a cercare in internet in DataSheet di questo componente per avere maggiori dettagli.
  • 27.
  • 28. Così come il montaggio di una automobile può essere diviso in tante operazioni che vengono svolte in parallelo, anche l' esecuzione di una istruzione di processore viene suddivisa in operazioni eseguite contemporaneamente.
  • 29.
  • 30. Gli unici accessi alla memoria sono di tipo LOAD e STORE
  • 31. Il formato delle istruzioni è semplice e tipicamente di lunghezza fissa.
  • 32.
  • 33. Decodifica e lettura dei registri: (ID) L'istruzione viene decodificata e gli operandi vengono letti dai registri Nel caso di una istruzione di salto (BRANCH) viene calcolato l'indirizzo ove fare il salto e in alcune architetture viene eseguito. La decodifica dell'istruzione e lettura in parallelo sono possibili per il semplice formato delle istruzioni.
  • 34. Esecuzione o calcolo dell'indirizzo effettivo: (EX) La ALU opera sui dati preparati nel ciclo precedente : Riferimento alla memoria: Somma base+offset per ottenere l'indirizzo effettivo Operazione fra registri: L'ALU esegue l'operazione indicata dall'OpCode Operazione registro-Dato: L'ALU esegue l'operazione indicata dall'OpCode
  • 35. Accesso alla memoria: (MEM) Se l'istruzione è un LOAD la memoria viene letta, se è uno STORE scritta alla locazione calcolata precedentemente.
  • 36. Accesso ai registri: (WB) Nel caso di istruzioni di LOAD o istruzioni da fra registri dell'ALU il risultato dell'operazione viene scritto nel registro.
  • 37. In questo tipo di implementzione le istruzioni di BRANCH vengono eseguite in due cicli, quelle di STORE in 4 e tutte le altre in 5. Supponendo che vi sia un 12% di BRANCH e un 10% di STORE calcolate quanti cicli in media richiede un'istruzione. Ciascuna istruzione viene eseguita più lentamentamente ma si un'accelerazione complessiva
  • 38. Problemi nell'uso della Pipeline. Sono chimati Hazards e sono di tre tipi: 1) Di tipo strutturale: Quando l'hardware non può gestire tutte le possibili combinazioni di istruzioni sovrapposte. 2) Dovuti ai dati: a) Sono dovuti alla dipendenza di un'istruzione ( i ) dal risultato di una seguente ( j ). - Tipo RAW (Read after Write) j tenta di leggere un dato prima i l'abbia scritto. - Tipo WAW (Write after Write) j scrive un risultato prima di i . - Tipo WAR (Write after Read) j scrive un risultato prima che i lo legga. Nel tipo di pipeline statica che stiamo presentando (scrittura in un solo stadio e nessun riordino delle istruzioni) si ha solo il tipo RAW. b) (name dependency) all'uso di uno stesso registro di memoria senza però un reale passaggio di dati fra due istruzioni 3) Dovuti al controllo di flusso: Sono dovuti alle istruzioni di BRANCH condizionato. Questo tipo di problemi obbliga a fermare la pipeline finchè il problema non si è risolto limitandone così le prestazioni.
  • 39. Data Hazards: Considerate il seguente programma: Dadd R1,R2,R3 // Somma R2+R3 ----> R1 Dsub R4,R1,R5 // Differenza R1-R5 ----> R4 And R6,R1,R7 // AND R1&R7 ----> R7 . . etc...... Tutte le istruzioni dopo la prima usano il suo risultato ! Ma mantre Dsub legge R1 nel suo stadio ID, Dadd lo scriverà solo durante il WB. Se non viene gestito correttamente il problema porta ad un risultato non deterministico del valore letto da Dsub. (Potrebbe esserci stato un interrupt tra Dadd e Dsub) Questo tipo di problemi viene risolto nei processori moderni con una tecnica chiamata forwarding . Tralasciando I dettagli si tratta di scrivere il risultato di Dadd non solo in R1 (durante il WB) ma subito dove l'istruzione Dsub ne ha bisogno ovvero in entrata all'ALU. Ma non sempre questo trucco funziona.
  • 40. L'accesso alle memoria MEM produce il suo risultato tramite il forwarding solo alla fine del ciclo di CLOCK mentre EX lo richiede all'inizio. Quindi è necessario fermare per un ciclo la pipe line Vi sono tecniche avanzate di ottimizzazione chiamte Dinamic scheduling o Out of Order execution ove le istruzioni sono eseguite in un ordine diverso da quello originale. Questo introduce nuovi possibili problemi di tipo WAR (WRITE after READ) e WAW (Write after Write).
  • 41. Control Hazards: Sono quei problemi dovuti a salti condizionati all'interno del programma. Vi sono diverse strategie. Una di queste scegliere un'istruzione da eseguire subito dopo quella di BRANCH in modo da prendere la decisione in seguito se fare o non fare il salto. Questa politica è chiamata “Branch delay slot”. Sta al compilatore scegliere che istruzione mettere. Questo è chiamato anche “static branch prediction”. Esitono e dobbiamo almeno citarle senza vederle in dettaglio tecniche di “dinamic branch prediction” ove, per esempio , le decisioni prese per ciascun branch viengono registrate (branch prediction buffer) e la predizione viene espressa in base alla storia passata. (profiling)
  • 42.  
  • 43. Per ottenere il massimo di prestazioni la pipeline deve essere costantemente tenuta piena. Per far questo bisogna sfruttare al massimo il parallelismo fra le istruzioni trovando nel programma sequenze di istruzioni non correlate fra loro che possono essere quindi sofrapposte all'interno della pipeline. Per evitare lo stallo due istruzioni dipendenti i e j devono essere separate fra loro almeno per un numero di clicli di clock tale da permettere a i di produrre un risultato utile. Consideriamo il seguente loop: For (i=1000; i>0; i=i-1) x[i]=x[i]+s ; Una traduzione letterale del codice in linguaggio macchina è assolutamente poco efficente. Non solo contiene un branch ma le dipendenze interne fra le istruzioni provocano una serie di stalli forzati della pipeline.
  • 44. In fase di compilazione si può usare una tecnica chiamata LOOP UNROLLING, ovvero lo scrivere esplicitamente quali righe del programma le linne del ciclo fin quando il numero di registri disponibili lo permette. x[1]=x[1]+s ; x[2]=x[2]+s ; x[3]=x[3]+s ; . . . In questo modo si evitano I branch e le continue sottrazzioni all'indice I e il codice diviene. Anche questo ha degli stalli ma I diversi LOAD e somme sono indipendenti e quindi..........
  • 45. Posso riordinare il codice senza interferire sul suo risultato ottenendo: Questo ciclo non ha nessun stallo. L'aver esplicitato I cicli del LOOP permette di aver più istruzioni e quindi di poterle riordinare in modo più efficente. Una ottimizzazione del genere può sembrare banale ma non lo è. Richiede due tecniche di intelligenza artificiale: sostituzione simbolica e semplificazione. In questo le espressioni possono essere riscritte in modo da ridurre tutte le costanti ovvero: ((A+1)+1) viene riscritta come (A+(1+1)) e semplificata in (A+2) già a livello di compilazione.
  • 46. Ma non tutto è sempre così semplice. Consideriamo il ciclo: For (i=1; i<=100; i++;) { A[i+1]=A[i]+C[i]; // S1 B[i+1]=B[i]+A[i+1]; // S2 } Qui abbiamo 2 tipi di dipendenza (S1) di cui una è detta Loop Carried ovvero dipendente dal ciclo. Inoltre si ha una dipendenza interna in ciascuna linea. In questo caso le iterzioni successive ad essere eseguite in serie. Il secondo tipo di dipendenza (S2 che fa dipendere le due righe fra loro) non dipende dal ciclo quindi diverse istanze dello stesso ciclo potrebbero essere eseguite parallelamente. Non tutte le dipendenze Loop Carried impediscono il parallelismo.
  • 47. For (i=1; i<=100; i++;) { A[i]=A[i]+B[i]; // S1 B[i+1]=C[i]+D[i]; // S2 } Consideriamo il ciclo: Vi è una dipendenza della prima riga sulla seconda attraverso il loop ma la seconda riga non dipende dalla prima quindi le due righe potrebbero essere scambiate fra loro. Alla prima iterazione S1 dipende dal valore B[1] calcolato esternamente al loop. A[1]=A[1]+B[1]; For (i=1; i<=99; i++;) { B[i+1]=C[i]+D[i]; A[i+1]=A[i+1]+B[i+1]; } B[101]=C[100]+D[100]; Non vi è più nessuna dipendenza Loop Carried e il loop può essere eseguito in modo parallelo.
  • 48. Quale ultimo caso vediamo quello di un'equazione ricorsiva: For (i=1000; i>0; i=i-1) x[i]=x[i]+x[i-1] ; Ovvero di una equazione ove una variabile è dipende da un suo valore definito in una iterazione precedente. Alcune architetture ( vettoriali ) sono particolarmente adatti ad eseguire calcoli ricorsivi e in alcuni casi vi è ancora spazio per il parallelismo: For (i=1000; i>5; i=i-1) x[i]=x[i]+ X[i-5] ; In questo caso si parla di una distastanza di dipendenza pari a 5. Più è lunga questa distanza più è possibile sfruttare il parallelismo tramite il loop unrolling. Sperando di aver dato le basi..... qui ci fermiamo riguardo a questo argomento. Dovremo ancora parlare di Architetture Vettoriali, Gerarchia della Memoria e Software Engineering.....
  • 49. Abbiamo fin qui visto (o per meglio dire accennato) a come sviluppare le capacità di calcolo di una CPU tramite l'ILP (Istruction Level Parallelism). Esistono limiti pratici a realizzare CPU con pipelines sempre più profonde (maggiore complessità, registri, numero di transistor......) I processori vettoriali sono stati sviluppati già prima della implementazione dell'ILP. Una istruzione vettoriale è in grado di sostituire un intero ciclo. La parte di IF e ID sono più complesse ma la parte di esecuzione è estremamente più semplice. Non ci possono essere più dei “Control Hazards” dovuti al loop. Usando le istruzioni vettoriali tutte le dipendenze Loop Carried devono essere state risolte dal programmatore. I “data hazards” vengono analizzate una sola volte per ogni vettore non per ogni elemento. Gli accessi alla memoria avvengono secondo schemi predefiniti riducendo la latenza e aumentando la larghezza di banda. Non tutte le applicazioni possono trarre vantaggio da vettoriali ma molte di quelle scientifiche sì (e anche i videogiochi ).
  • 50. Nel 2001 I calcolatori vettoriali sembravano sparire...... ma nel 2002 in giappone venne presentato l'Earth Simulator basato NEC SX-6. http://www.es.jamstec.go.jp/index.en.html Questa macchina ha 5120 processori vettoriali da 8GFlop ed è stata al primo posto al mondo per potenza di calcolo fino al 2004 con una potenza totale di 35.86 Tflop. Ora è al 20 posto superata da IBM ma..... I nuovi processori vettoriali della macchina SX9 annunciati da NEC hanno prestazioni che superano I 100 Gflop per una potenza totale (annunciata) di 839 Tflops. http://www.nec.co.jp/press/en/0710/2501.html
  • 51. Architettura (di principio ) di un processore vettoriale.
  • 52. Confrontando I due codici macchina la semplificazione risulta evidente. Supponiamo di dover fare l'operazione vettoriale: Y = Y +a* X
  • 53. “Il processore G92 ha 112 cores con clock 1.5Ghz, il G80 ha 128 cores con clock a 1.3Ghz. Ogni core puo' effettuare una moltiplicazione e una addizione per clock. Peak(G92)=112*1.5*2=336GFlops, Peak(G80)=128*1.3*2=332GFlops C'e' poi altro hardware ( per calcolare funzioni come sin, cos, exp) piu' interpolatori per textures.” Le moderne shede grafiche hanno processori vettoriali con capacità di calcolo impressionanti.
  • 54.  
  • 55. La trasparenza precedente ci introduce all'Ingegneria del Software. Questo termine definisce l'applicazione di approccio, ordinato, quantificabile e sistematico allo sviluppo, mantenimento, e messa in opera del software. Venne introdotto nel 1968 nella NATO software conference. Questa disciplina riguarda la raccolta delle specifiche, la progettazione, la costruzione, il test e il mantenimento dei programmi e raggruppa conoscenze da moltissimi campi. Il Software non è come un qualunque prodotto industriale: Si Sviluppa, Non si fabbrica. Ovvero Il software evolve costantemente nel tempo e quando questo non avviene “si deteriora”.
  • 56. La parte di codifica ovvero la scrittura del software in un qualunque linguaggio è solo una piccola parte del proceso di sviluppo. La parte di progettazione che viene prima della codifica è di estrema importanza. Una volta prese alcune scelte architetturali queste NON potranno essere più cambiate in corso d'opera a meno di costi rilevanti.
  • 57. In questo corso daremo solo alcuni principi base di ingegneria del software e una breve introduzione al linguaggio UML che come vedremo ci permetterà di modellizzare il software prima di srivererlo. Il primo principio che vorrei che aveste ben presente è quello di documentare il software che scrivete. Questo lavoro è assolutamente necessario. Ignorarlo porta alla costruzione di codici non mantenibili e potenzialmente pericolosi. In TUTTI gli esperimenti spaziali questo è assolutamente richiesto per poter volare. La documentazione deve iniziare iniziare sin dalle prime fasi di progettazione.
  • 58. Vi sono molti MODELLI di SVILUPPO del software. I più comuni sono conosciuti come a cascata , a spirale e a V . Il modello a cascata è illustrato dal diagramma. Ha il limite di non essere flessibile specialmente se le richieste cambiano nel tempo I test sono separati dal progetto.
  • 59. Richieste Analisi Progetto Codifica Test Accettazione Nel modello a spirale le diverse fasi vengono ripetute più volte passando attraverso la creazione di diversi prototipi che tenderanno sempre più verso il sistema finale. In TUTTI I modelli ciascuna fase viene documentata !
  • 60. Il modello a V è molto usato anche nella progettazione di sistemi.
  • 61. Per sistemi ad alta criticità quali quelli relativi alla Difesa, allo Spazio ma anche altri quali per esempio il software che deve gestire una metropolitana esistono METODI FORMALI, espressi matematicamente, che consentono di DIMOSTRARE la correttezza del software prodotto, ovvero il rispetto delle specifiche iniziali. La possibilità di usare questi metodi deriva dal fatto che un documento di specifica scritto in modo formale e privo di ambiguità contiene lo stesso quantitativo di informazione che il programma finale. Il codice prodotto quindi altro non è che una descrizione del problema che si vuole risolvere. Il più famoso fra questi metodi è stato prodotto da IBM ed è noto col nome di “CleanRoom”. Questo metodo di lavoro cerca di PREVENIRE gli errori piuttosto che correggerli integrando il test all'interno dello sviluppo
  • 62. Già da molto tempo si sono rappresentati gli algoritmi tramite I cosiddetti “diagrammi di flusso” o flow chart. A Lato vedete un esempio (non formale....) di un diagramma di flusso di un programma che calcola il fattoriale. (Notate che è una funzione ricorsiva). Similmente già da tempo esistevano programmi che permettevano di tradurre I diagrammi di flusso in codice sorgente.
  • 63. Attualmente si usa il linguaggio UML ovvero l'UNIFIED MODELLING LANGUAGE non solo per rappresentare fra loro le classi di un programma C++ (a lato un esempio di relazioni fra classi) ma interi sistemi sia nei loro aspetti strutturali che nei loro aspetti di interazione ed evoluzione temporale.
  • 64. A lato vediamo un esempio di diagramma UML usato per rappresentare l'interazione di due attori (cliente e venditore) con un sistema di gestione degli ordini.
  • 65. Nel moderno UML I vechi diagrammi di flusso possono essere visti come “Activity diagrams”
  • 66. I diversi componenti fisici del sistema possono essere modellizzati con un Deployment Diagram.....
  • 67. Ma si può modellizzare ben oltre un semplice programma C++ Una reale trattazione del linguaggio UML è oltre lo scopo di questo corso ma è importante che prendiate coscienza che questo tipo di strumento è presente nei moderni ambienti di lavoro ed è estremamente utile sia nella fase progettuale che di documentazione. Vi invito quindi a scaricare uno dei tanti editor UML gratuiti e a provare a documentare una vostra piccola applicazione tramite il linguaggio UML. Per esempio per Windows, MacOsX va molto bene Visual Paradigm nella community edition. Ne esistono anche diversi altri ma non sono tutti gratuiti ( Magicdraw ha una versione gratis). Per Linux potete usare Umbrello che viene distribuito con KDE o consultare questo articolo : http://www.linux.com/articles/47431 per avere una maggiore scelta.
  • 68. 1. template <class T> 2. class data{ 3. T d; 4. T err; 5. public: 6. data() {d=(T)0.; err=(T)0.;} 7. data(const T a, const T b){d=a; err=b;} 8. data(const data &old){d=old.d; err=old.err;} 9. ~data(){}; 10. data& operator=(const data &old){d=old.d; err=old.err; return *this;} 11. data& operator+=(const data &A){ 12. data result; 13. d=d+A.d; 14. err=err+A.err; 15. return *this; 16. } 17. const data operator+(const data &A){ 18. data result; 19. result.d=d+A.d; 20. result.err=err+A.err; 21. return result; 22. } 23. T getd() {return d;} 24. T geterr() {return err;} 25. void setd(const T a){d=a;} 26. void seterr(const T a){err=a;} 27. };
  • 69. 1. template <class T> 2. class logbook{ 3. public: 4. logbook(){n=0;} // Il creatore vuoto non deve fare nulla. 5. logbook(const class data<T> A){n=0; lemisure.push_back(A); n++;} // Il creatore con un argomento inserisce il primo elemento 6. logbook(const logbook &old){ 7. int i; 8. n=old.n; 9. lemisure.erase(lemisure.begin(),lemisure.end()); //svuoto il vettore di partenza 10. for(i=0;i<n;i++){lemisure.push_back(old.lemisure[i]);} //e lo riempio coi nuovi dati 11. } 12. ~logbook(){}; 13. /* 14. Di nuovo ridefinisco l'operatore = 15. */ 16. logbook& operator=(const logbook &old){ 17. int i; 18. n=old.n; 19. lemisure.erase(lemisure.begin(),lemisure.end()); 20. for(i=0;i<n;i++){lemisure.push_back(old.lemisure[i]);} 21. return *this;} 22. int getn(){return n;}; 23. void addm(const class data<T> A){ 24. lemisure.push_back(A); 25. n++; 26. } 27. /* 28. Un esempio (molto banale) di funzione che opera sui dati. */ 29. T mean(){int i; 30. T media; 31. media=0; 32. for(i=0;i<n;i++){media=media+lemisure[i].getd();} return media /( T ) n; 33. } 34. private: 35. vector<class data<T> > lemisure; 36. int n; 37. };
  • 70. Cache memory: è una memoria veloce e di piccole dimensioni fra il processore e la RAM. Un uso corretto della cache (i.e. accedere a informazioni vicine fra loro) può velocizzare in modo significativo i vostri programmi !
  • 71. #include <stdlib.h> #include <stdio.h> #include <time.h> #define Nu 10000 int main() { clock_t ticks1, ticks2; int i,j; double *A; A = ( double *)malloc(Nu*Nu* sizeof ( double )); ticks1=clock(); // inizializza il timer for (i= 0 ;i<Nu;i++){ for (j= 0 ;j<Nu;j++){ *(A+(j*Nu+i))=i+j; } } ticks2=clock(); // secondo timer printf( &quot; Tempo in millisecondi : %ld &quot; ,(ticks2-ticks1)* 1000 /CLOCKS_PER_SEC); } Provate questo codice. La Cache è usata bene ? Si può renderlo più performante ?
  • 72. for (i= 0 ; i<Ndim; i++){ for (j= 0 ; j<Mdim; j++){ tmp = 0.0 ; for (k= 0 ;k<Pdim;k++){ /* C(i,j) = sum(over k) A(i,k) * B(k,j) */ tmp += *(A+(i*Ndim+k)) * *(B+(k*Pdim+j)); } *(C+(i*Ndim+j)) = tmp; } } Similmente questo frammento di codice esegue una moltiplicazione di matrici. Supponiamo di avere matrici piuttosto grandi (e.g. double 1000x1000 ) La Cache è usata bene ? Come potrei migliorare questo codice ?
  • 73. Accenniamo ora, almeno a livello introduttivo ad alcuni concetti riguardanti la programmazione parallela. Questo tipo di argomenti diverrà sempre più importante con la diffusione dei processori multicore e delle macchine multiprocessore. Abbiamo già accennato a questi argomenti quando abbiamo parlato delle schede grafiche (GPU) usate per calcolo scientifico. In quel caso avevamo già introdotto la parola “Thread”, e se vi ricordate ne abbiamo anche parlato quando abiamo visto gli “Activity Diagrams” dell'UML, senza però darne una definizione precisa. Il materiale in questa lezione è ricavato in gran parte da un seminario WEB di INTEL che sta facendo un grande sforzo per rieducare I programmatori ad usare metodi paralleli.
  • 74. “ Istituzioni di Sistemi Operativi”. Si chiama PROCESSO una unità di lavoro, gestita dal sistema opertivo che ha un proprio spazio di memoria (address space(heap)) e le risorse che gli vengono messe a disposizione dal sistema operativo. Si chiama THREAD una sotto unità di lavoro all'interno di un processo. Un thread ha un proprio program counter e stack ma condivide tutte le altre risorse col processo che lo ha generato. I Thread sono le unità naturali di lavoro per gli ambienti con memoria condivisa (noti anche come UMA Uniform Memory Access). Dato che condividono le risorse NON dobiamo dividere I dati del nostro problema in più parti.
  • 75. Un esempio (da voi ben conosciuto !) di problema parallelizzabile:
  • 76. Il programmino per calcolare Pigreco: [ CONTIENE UN ERRORE ! Dove ? ] #include <iostream> using namespace std; static long num_steps = 1000000; double step; void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); } pi = step * sum; cout << &quot; Ed ecco a voi Pi: &quot; << pi<< endl; cin >> i; return 0; }
  • 77. In questa lezione vedremo solo due esempi su come parallelizzare questo programma: L'uso delle direttive di compilazione OpenMP, e le librerie MPI (Message passing Interface) Non trattiamo le librerie per gestire direttamente I thread da sistema operativo (Win32 o Pthreads) perchè un pò' più complesse. Nella pagina web del corso trovate i link per approfondire l'argomento. Open MP è una serie di direttive per il compilatore e librerie per costruire applicazioni parallele. Rendono facile creare processi con più thread partendo da programmi Fortran C e C++ esistenti. Sono diventate uno standard negli ultimi 15 anni.
  • 78. Il modello di programmazione in OpenMP è chiamato “Fork-Join”: Un Thread principali lancia gruppi di thread paralleli, Il parallelismo può essere introdotto in modo increamentale nel programma esistente facendo evolvere le parti sequenziali in codice parallelo (lì dove è possibile). Thread principale Zone Parallele Zone Scalari
  • 79. Non dovete spaventarvi quando parlo di “direttive del compilatore”. In realtà le state già usanddo da tempo. Quando scrivete #INCLUDE <IOSTREAM> avete scritto qualcosa che NON è nel linguaggio C++ ma è una istruzione al compilatore perchè includa quelle librerie nel vostro programma. Quasi tutto il lavoro di OpenMP viene fatto tramite direttive date la compilatore. Sono poche le funzioni da aggiungere al codice. Per esempio con: #pragma omp parallel { ///Tutti I thread eseguono le istruzioni in questo blocco. } Oppure #pragma omp parallel for for(i=0;i<N;i++) { /// Ciascun thread riceve parte dei loop } Vi sono anche direttive per modificare il comportamento rispetto alle variabili: Private (lista....) crea una copia privata delle variabili per ciascun thread, Reduction(Op: lista) esegue una operazione di riduzione locale tramite l'operatore OP della lista di variabili.
  • 80. #include <iostream> #include <string> #include <stdio.h> #include <omp.h> using namespace std; static long num_steps = 1000000 ; double step; #define NUM_THREADS 2 int main () { int i; double x, pi, sum = 0.0 ; step = 1.0 /( double ) num_steps; omp_set_num_threads(NUM_THREADS); #pragma omp parallel for reduction(+:sum) private(x) for (i= 1 ;i<= num_steps; i++){ x = (i- 0.5 )*step; if (sum== 0 ) printf( &quot;Sono nel ciclo For parallelo : %d %d&quot; , omp_get_num_threads(), omp_get_thread_num()); sum = sum + 4.0 /( 1.0 +x*x); } pi = step * sum; cout << &quot;Ed ecco a voi: &quot; << pi << &quot; Numero di trhead:&quot; << omp_get_num_threads() << endl; return 0 ; } Notate l'operazione di riduzione sulla variabile sum e il fatto che la variabile x sia tenuta privata. Domanda: Perché ho usato printf invece che cout nel ciclo ?
  • 81.  
  • 82. MPI è un'API per scrivere applicazioni su cluster. In questo caso non siamo più su architettura a memoria condivisa. (NUMA :non uniform memory access) MPI è una libreria che consente comunicazioni collettive o punto punto fra processi e ne permette di coordinarne l'esecuzione. E' uno standard e raccoglie l'esperienza di 15 anni di lavoro su cluster e MPP (Massively Parallel Programs). Ne esiste una versione free chiamata MPIch.
  • 83. MPI è adatto anche ad ambienti MIMD. All'inizio di ogni programma MPI bisogna aggiungere 3 chiamate obbligatorie: MPI_Init(&argc, &argv) ; MPI_Comm_Rank(MPI_COMM_WORLD, &my_id) ; MPI_Comm_Size(MPI_COMM_WORLD, &numprocs) ; Queste tre chiamate inizializzano l'ambiente e forniscono l'identificativo del processo e quanti processi sono in esecuzione. Una volta saputo questo ogni processo può agire in modo indipendende in base al suo ID. MPI implica la modificazione del codice sorgente. In MPI le uniche informazioni che vengono scambiate fra I processi sono I messaggi. Nulla è condiviso. Spesso in un programma possiamo trovare un unica operazione di riduzione MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD) ; Con questa chiamata si dice di prendere I valori locali di sum e ridurli tramite l'operatore MPI_SUM nella variabile pi mandando la risposta al processo 0.
  • 84. #include <stdio.h> #include <stdlib.h> #include <mpi.h> int main ( int argc, char *argv[]) { int num_steps; int i, my_id, numprocs; double x, pi, step, sum ; int rank, size, length; char name[BUFSIZ]; double my_steps; MPI_Init(&argc, &argv) ; MPI_Comm_rank(MPI_COMM_WORLD, &my_id) ; MPI_Comm_size(MPI_COMM_WORLD, &numprocs) ; MPI_Get_processor_name(name, &length); sum= 0.0 ; num_steps= 100000 ; my_steps = num_steps/numprocs ; step = 1.0 /( double ) num_steps ; for (i=my_id*my_steps; i<(my_id+ 1 )*my_steps ; i++) { x = (i+ 0.5 )*step; sum += 4.0 /( 1.0 +x*x); } sum *= step ; MPI_Reduce(&sum, &pi, 1 , MPI_DOUBLE, MPI_SUM, 0 , MPI_COMM_WORLD) ; MPI_Finalize(); printf( &quot;Ed ecco a voi PI : %f %f Dal processo: %d e PROCESSORE %s&quot; ,pi,sum,my_id,name); exit( 0 ); } Ecco quindi il codice del nostro programma notate come si sia dovuto modificare il ciclo for. Questo codice funziona ed è stato provato con MPICH2.
  • 85. Questo tipo di approccio è chiamato anche SPMD (single program multiple data). Un grande insieme di dati viene diviso in pezzi più piccoli. Bisogna però aggiungere del codice al programma originale in modo che ciascuna istanza di questo possa operare su ciascuna porzione di dati.
  • 86. MPI e OpenMP possono essere usati insieme. Questo è perfettamente logico se ho un cluster di machine ciascuna delle quali ha più processori o più core. Prima modifichiamo il codice sorgente per usare MPI poi parellelizziamo I loop rimasti usando OpenMP.
  • 87. #include <mpi.h> #include “omp.h” void main (int argc, char *argv[]) { int i, my_id, numprocs; double x, pi, step, sum = 0.0 ; step = 1.0/(double) num_steps ; MPI_Init(&argc, &argv) ; MPI_Comm_Rank(MPI_COMM_WORLD, &my_id) ; MPI_Comm_Size(MPI_COMM_WORLD, &numprocs) ; my_steps = num_steps/numprocs ; #pragma omp parallel for private(x) reduction(+:sum) for (i=myrank*my_steps; i<(myrank+1)*my_steps ; i++) { x = (i+0.5)*step; sum += 4.0/(1.0+x*x); } sum *= step ; MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0,MPI_COMM_WORLD) ; Questo è il programma che si ottiene combinando fra loro i due approcci.
  • 88. Quale approccio usare ? Se si ha a disposizione un cluster la scelta standard è MPI. MPI chiede poco dal punto di vista hardware e quindi è molto facilmente implementabile. OpenMP può essere usato per parallelizzare del codice esistente su architetture a memoria condivisa. Bisogna però prestare molta attenzione ai possibili conflitti fra un thread e l'altro ! Memoria condivisa vuol dire che tutte le variabili sono normalmente condivise fra tutti I thread e quindi un thread potrebbe aggiornare una variabile usata da un'altro. OpenMP vi consente di concentrarvi al massimo sulla vostra applicazione mettendo in sott'ordine le API e la struttura di dati, mentre MPI vi costringere a modificare la vostra applicazione riprogettandola. Solo se state progettando delle librerie di sistema e davvero volete avere il massimo di controllo su ogni aspetto della gestione dei Thread avrete bisogno di usare le librerie messe a disposizione dal sistema operativo.