View Single Post
Old 25-11-2009, 16:55   #8309
yossarian
Senior Member
 
Iscritto dal: Mar 2001
Messaggi: 5390
Quote:
Originariamente inviato da skizzo99999999 Guarda i messaggi
Non voglio intromettermi nella diatriba tra devAngnew e yossarian per creare altro casino, quindi spero che quello che dirò cercando di argomentare al mio meglio non generi altro flame, anche se finchè la discussione rimane su toni civili (che mi sembra ci siano ancora) mi sembra sopportabilissima. Premetto che non ho letto l'articolo pluricitato di Rys, ma soltanto i recenti post riguardanti la questione MADD/FMA. Ora io non conosco in modo approfondito l'architettura delle GPU e relative problematiche, e quindi non pretendo di essere la bibbia dell'argomento in questione. Io conosco abbastanza bene l'ambiente OPENGL, che non penso differisca molto (almeno per il problema che ci riguarda al momento) da quello directx.
Quando devo caricare uno shader program (composto, per esempio, sia da fragment che da vertex shader), devo eseguire:

glCreateShader(GL_VERTEX_SHADER);
glCreateShader(GL_FRAGMENT_SHADER);

che servono a "creare" gli shader. Poi un paio di LoadShaderText per caricare i file di testo che contengono il codice GLSL dei 2 shader. Bisogna tenere presente che il GLSL è come il c: è un linguaggio ad alto livello, anche se non così "alto" e così versatile come il c visto che le GPU hanno dei limiti sul codice da poter eseguire. C'è da dire che però le operazioni vettoriali sono molto più semplici in GLSL che in c, ma vabbe... questo è una altro discorso. Tornando in tema, dopo aver caricato i file di testo che contengono i 2 vertex e fragment shader, faccio 2 glCompileShader per compilare gli shader; poi creo un contenitore per per "unire" vertex e fragment in un unico programma tramite la glCreateProgram seguita da 2 glAttachShader (una per il fragment e una per il vertex). Ora mi ritrovo quindi un "programma", compilato, che contiene i 2 shader; rimangono le operazioni di link e validate da eseguire tramite la glLinkProgram e la glValidateProgram, che devono essere eseguite sul nuovo "programma" e non sui 2 shader separati.
Tutta questa sequenza la eseguo soltanto UNA volta durante il caricamento del programma, in cui leggo, compilo, linko, ecc... tutti gli shader che devo eseguire, in modo che se ho sbagliato qualcosa lo so subito e non mi si pianta il programma dopo mezz'ora. Può essere che nei progetti di grosse dimensioni gli shader siano in numero tale che sia preferibile un caricamento differito, ma sicuramente anche se così fosse avverrebbe sempre durante, per esempio, il caricamento di un livello successivo a quello attuale e non quando lo/gli shader li devo effettivamente usare.
Infatti quando li devo utilizzare, mi basta fare una

glUseProgram(GeometryProgram);

per caricare lo shader, già compilato, linkato, ecc... con i passi descritti in precedenza. Se così non fosse, hai voglia a dover ricompilare il GLSL decine di volte durante ogni frame, visto che anche nel mio deferred renderer carico una decina di shader diversi (tramite semplici glUseProgram) a seconda delle fasi di z-culling, geometry stage, light_pass o postprocessing in cui mi trovo...
Concludendo questa lunga introduzione, non mi sembra così impossibile che il compilatore del GLSL, risiedendo nel driver video (di questo ne sono sicuro) e che quindi conosce la scheda video che monta la macchina, possa sostituire durante la compilazione degli shader le MADD con delle FMA. Questo ovviamente tralasciando i problemi relativi alle imprecisioni derivanti dagli arrotondamenti diversi, ma non mi sembra questo il motivo del contendere. Ora (ed è qui il mio dubbio) non so se il codice compilato GLSL corrisponda al codice contenente le MADD MUL, ecc... ma mi sembra ragionevole che se il codice c che viene compilato dal compilatore c (che NON compila anche il codice dello shader, ma solo il sorgente x86, se ovviamente siamo su una macchina x86) diventa un eseguibile, anche il GLSL una volta compilato tramite il compilatore del driver video (tramite i passaggi di cui sopra) diventi un eseguibile per la GPU.
Certo è che come il codice compilato dal c non viene eseguito pari pari dalle CPU, anche il codice compilato dal GLSL non viene eseguito brutalmente, ma viene poi "ottimizzato" per adattarsi meglio all'architettura interna e rendere possibili le esecuzioni fuori sequenza, branch prediction e tutte queste belle cosucce che a livello di compilazione statica sono impossibili o quasi da eseguire.
Quindi non è che se si potesse fare questa benedetta "ottimizzazione" MADD/FMA allora non esisterebbe il problema della ottimizzazione, per esempio, di una architettura per sua natura difficile da struttare in pieno come quella di ATI; devo dire che mi sembrano due problematiche ben differenti. La mera sostituzione di una operazione MADD/FMA è una "ottimizzazione" che coinvolge quella istruzione soltanto, mentre risolvere problemi come la branch prediction, l'esecuzione fuori sequenza oppure gestire il problema di come utilizzare tutte le ALU di rv870 riguarda il trovare le dipendenze fra le istruzioni, il che se mi permettete è tutto un'altro paio di maniche.
Poi per carità un conto è dirlo e un conto è farlo, ma non mi sembra così impossibile che ci riescano senza avere un hit prestazionale.
finalmente, al termine della vicenda, è venuto fuori dove si trova questo benedetto compilatore
Quello che hai detto è tuto giusto e infatti è quello che avviene. Ma un conto è scrivere codice dicendo:"al posto dell'istruzione x esegui l'istruzione y", un altro è fare in modo che questo avvenga senza hit prestazionali.
Tu hai descritto quello che si fa quando si opera uno shader replacement o anche la semplice sostituzione di un'istruzione, a livello di compilatore. Però il codice che arriva alla gpu non è scritto, nel caso specifico con l'istruzione y ma arriva con l'istruzione x; la gpu si accorge che gli è arrivata l'istruzione x solo dopo che l'ha prelevata, l'ha letta e l'ha decodificata. Finchè non capisce di cosa si tratta non può operare nessun tipo di sostituzione. Però sa che non deve eseguire l'istruzione x, quindi quando legge x la rimpiazza con y e questo deve farlo per tutte le x presenti nel programma. Ora, ragionando terra terra, all'interno di un chip ogni step richiede almeno un ciclo di clock. Quindi se "prelevo l'istruzione x dal registro, e la decodifico" ho impiegato almeno due cicli (uno per prendere l'istruzione e l'atro per leggerla). Se devo mandarla in esecuzione impiegherò almeno un altro ciclo. Se devo sostituirla anche (perchè quello che leggo è x non y e la trasformazione di x in y non è gratuita).
Quindi, un hit prestazionale per effettuare la sostituzione dell'istruzione comunque ci sarà.
yossarian è offline