Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 17
  1. #1
    Utente di HTML.it
    Registrato dal
    Jan 2015
    Messaggi
    103

    Step combilazione codice C# [era: dubbio codice di programmazione]

    salve ragazzi, è da un po che programmo ma non sono certo un veterano, ne tantomeno un "esperto" quindi anche se magari la domanda potrà sembrarvi stupida o banale non linciatemi XD detto ciò,l'altro giorno mi è venuto un dubbio,quando faccio un programma in c# e lo salvo,il codice di quel programma viene salvato in c# e poi al momento del lancio del programma viene convertito in codice c e dopo in codice macchina? o viene salvato direttamente in codice c? Grazie in anticipo per l'aiuto

  2. #2
    Utente di HTML.it L'avatar di MySQL
    Registrato dal
    May 2015
    Messaggi
    729
    terza opzione :di solito si compila c# in eseguibile direttamente

  3. #3
    Utente di HTML.it L'avatar di Scara95
    Registrato dal
    Jul 2009
    residenza
    Zimella (VR)
    Messaggi
    2,589
    Il codice C# viene salvato in formato testuale.
    Il codice viene poi compilato per il .NET Framework e "impacchettato" a dovere in un eseguibile che quando viene lanciato viene "passato" al .NET Framework, il quale lo interpreta e agisce di conseguenza.

    Il codice C, invece viene compilato in linguaggio assembly che poi viene convertito in linguaggio macchina da un assembler, il risultato viene impacchettato in un eseguibile che, dopo essere caricato in memoria dal sistema operativo, viene interpretato dalla macchina hardware.

    Il procedimento e sempre lo stesso. Infatti si può, in generale, parlare di due tipi di macchine astratte: compilative e interpretative.
    "Quid enim est, quod contra vim sine vi fieri possit?" - Cicerone, Ad Familiares

  4. #4
    Utente di HTML.it
    Registrato dal
    Jan 2015
    Messaggi
    103
    ahhh ok grazie, @Scara95 quindi il c# non viene convertito in codice assembly e poi macchina ma viene interpretato dal .Net Framework direttamente cosi? questo non ne risente sulle prestazioni? cioè se il c# venisse trasformato in codice macchina come con il c, non sarebbe migliore a livello di prestazioni? ultima domanda...compilativo sarebbe il c e interpretative il c#? grazie ancora

  5. #5
    C# di fatto viene compilato in bytecode MSIL (una specie di codice macchina per una "CPU virtuale" di più alto livello rispetto alle CPU vere); al momento dell'esecuzione, il bytecode MSIL viene JIT-compilato in codice macchina "vero" della CPU su cui sta girando il .NET Framework.
    Discorso velocità: dipende. La compilazione "just in time" ha meno tempo rispetto ad un compilatore "tradizionale" per provare ad ottimizzare in maniera aggressiva (visto che c'è lì l'utente che attende che il programma parta), ma ha una serie di vantaggi derivanti dal conoscere esattamente l'ambiente in cui viene eseguito il codice, per cui in linea di principio può emettere codice più efficiente per la specifica CPU su cui gira, o fare tracing/profiling al volo del codice per individuare i punti più "caldi" e ottimizzarli in base ai percorsi di codice più frequenti (da questo punto di vista ho visto fare cose egregie a LuaJIT - dal bytecode di un linguaggio estremamente dinamico come Lua riesce spesso a cavare fuori codice macchina che va a velocità dello stesso ordine di grandezza dell'equivalente C).
    Amaro C++, il gusto pieno dell'undefined behavior.

  6. #6
    Utente di HTML.it
    Registrato dal
    Jan 2015
    Messaggi
    103
    @MItaly grazie mille credo di aver capito...quindi diciamo che un compilatore JIT come quello del c# teoricamente è più lento perchè deve effettuare una conversione in più, ma che poi alla fine riesce a recuperare quella perdita di tempo ottimizzando meglio il codice,essendo JIT e potendo vedere ad esempio la CPU sulla quale viene eseguito il codice,o come hai detto tu ottimizzare i punti più "caldi" metre invece una compilazione "tradizionale" come quella del c non si presta a ottimizzazzioni essendo che è già codice macchina pronto per essere avviato, giusto? grazie ancora

  7. #7
    Utente di HTML.it
    Registrato dal
    Jan 2015
    Messaggi
    103
    altra domanda rapida visto che sei entrato in argomento LUA, vorrei approcciarmi al mondo dell'intelligenza artificiale, ripeto ho delle basi buone di programmazione in c,c++,java,html,c#,java script,css e anche buone basi di sistemi informatici, ma nulla di troppo avanzato, potresti consigliarmi qualche libro/manuale o simili per iniziare?

  8. #8
    Utente di HTML.it L'avatar di Scara95
    Registrato dal
    Jul 2009
    residenza
    Zimella (VR)
    Messaggi
    2,589
    C e me ottimizzazioni vanno a braccetto, il problema è quanto specifico è l'ISA per cui compili, più è specifico, più le prestazioni saranno alte, meno è specifico, più le cpu supportate.

    Per specifico non intendo specificato, intendo che processori simili (della stessa famiglia) hanno set di istruzioni simili, ma alcuni hanno istruzioni che ad altri mancano. Inoltre prese combinazioni di istruzioni equivalenti a e b, su un'implementazione può essere più veloce la combinazione a e su un altro può essere più veloce la combinazione b.

    Il vantaggio di un compilatore JIT è che conosce a perfezione l'HW e tutte le istruzioni disponibili.

    In generale andrà comunque meglio C perché ha più spazio per le ottimizzazioni, non ha nessuna forma di controllo della correttezza delle operazioni sulla memoria, ..., ...
    "Quid enim est, quod contra vim sine vi fieri possit?" - Cicerone, Ad Familiares

  9. #9
    Utente di HTML.it
    Registrato dal
    Jan 2015
    Messaggi
    103
    @Scara95 ok chiaro quindi il vantaggio della compilazione JIT è che conosce meglio l'hw su cui verrà compilato rispetto al c, ma il c andrà comunque sempre meglio (in termini di velocità di compilazione) perchè non ha nessun tipo di limitazione nell'ottimizzazzione giusto? ma le ottimizzazioni sul codice c vanno comunque fatte sempre dall'utente se conosce il tipo di hw sul quale va a girare il programma? Diciamo che in automatico non fa nulla a livello di ottimizzazione giusto?

  10. #10
    mi sembra che tu abbia molta confusione in testa.

    Ci sono tutta una serie di ottimizzazioni che sono indipendenti dall'hardware, per cui il compilatore non emette il codice che gli è stato detto, ma emette del codice che nel caso specifico farebbe la stessa cosa. Prendiamo per esempio:
    codice:
    #include <stdio.h>
    #include <math.h>
    
    static void check(double d) {
        if(d>1) printf("d>1\n");
        else     printf("d<=1\n");
    }
    
    int main() {
        double d=sqrt(2);
        check(d);
        return 0;
    }
    Il ragionamento è del tipo:
    • printf con un newline in fondo è equivalente a puts (che è più efficiente)
      codice:
      #include <stdio.h>
      #include <math.h>
      
      static void check(double d) {
          if(d>1) puts("d>1\n");
          else     puts("d<=1\n");
      }
      
      int main() {
          double d=sqrt(2);
          check(d);
          return 0;
      }
    • check è static, quindi tutte le chiamate che ci possono essere vengono solo da questo modulo; l'unica chiamata è dal main, quindi si può espandere in linea:
      codice:
      #include <stdio.h>
      #include <math.h>
      
      int main() {
          double d=sqrt(2);
          if(d>1) puts("d>1\n");
          else     puts("d<=1\n");
          return 0;
      }
    • sqrt(2) si può risolvere a compile-time:
      codice:
      #include <stdio.h>
      
      int main() {
          double d=1.4142135623730951;
          if(d>1) puts("d>1\n");
          else     puts("d<=1\n");
          return 0;
      }
    • d non cambia, si può propagare la costante:
      codice:
      #include <stdio.h>
      
      int main() {
          if(d>1.4142135623730951) puts("d>1\n");
          else     puts("d<=1\n");
          return 0;
      }
    • l'if è già risolto, il secondo branch può essere ucciso:
      codice:
      #include <stdio.h>
      
      int main() {
          puts("d>1\n");
          return 0;
      }

    L'assembly generato da gcc con -O3 in questo caso è equivalente all'ultimo blocco di codice - la questione è completamente risolta a compile-time.

    Il C e il C++ da questo punto di vista hanno vita più facile rispetto al C#, dato che le loro specifiche su certe questioni sono volutamente più vaghe e/o non impongono al compilatore di fare certi controlli (=> questione undefined behavior), per cui il compilatore è più libero di fare assunzioni che lo possono aiutare ad ottimizzare.
    Inoltre, come linguaggi C e C++ sono meno dinamici di un C# (per non parlare di linguaggi dinamici come Lua o Python), per cui tanto dello stato del programma è noto già a compile-time (esempio: in molti casi il compilatore C++ è in grado di devirtualizzare e/o espandere in linea chiamate a metodi virtuali; in linguaggi come Python dove si può fare monkeypatching a runtime questo è impossibile, ma anche in C# è complicato dal fatto che il grosso delle chiamate che si fanno sono a moduli esterni, che potrebbero essere differenti a runtime).

    Per cui, se in C molte di queste ottimizzazioni possono essere fatte subito, in C# in genere il compilatore si trova a generare il codice per il "caso generale" (anche se può già fare ottimizzazioni "sicure" di alto livello, come estrarre sottoespressioni comuni invece di ricalcolarle, riscrivere espressioni in modo equivalente ma più veloce - left shift invece di moltiplicazioni, prodotti per l'inverso in aritmetica intera invece di divisioni, ... -, eliminare dead code, espandere inline metodi sealed della stesso assembly, ...), lasciando al JIT da fare il lavoro sporco dopo.
    Al momento dell'esecuzione, infatti, il JIT trova più certezze rispetto al momento della compilazione - sa esattamente di che tipi sono gli oggetti con cui sta avendo a che fare, ha in mano le implementazioni effettive dei moduli da chiamare, eccetera. Il problema del JIT rispetto ad un compilatore "normale" è che ha poco tempo: un compilatore C++ può permettersi di perdere minuti ad ottimizzare un modulo, il JIT no, visto che l'utente è lì che aspetta che l'applicazione si carichi.

    Finora non abbiamo nemmeno sfiorato l'argomento codice macchina generato; anche qui, in teoria il JIT ha gli elementi per vincere, dato che, sapendo esattamente qual'è la CPU target, può aggiustare perfettamente il codice generato in base a quello che è veloce o meno sulla CPU corrente (ed eventualmente usare set di istruzioni opzionali disponibili sulla CPU), in pratica anche qui si ritorna al discorso del tempo.
    Un compilatore "tradizionale", anche se ha un target di processore un po' meno preciso, può perdere tempo a fare analisi e rielaborazioni complesse (ad esempio vedendo che certe istruzioni sono inutili, ottimizzando al meglio l'uso dei registri, rigirando il codice in modo che sia equivalente ma più semplice per la CPU, ...), un JIT in genere no. Per questo motivo il .NET Framework ha anche un compilatore ahead of time (ngen), da eseguire al momento dell'installazione del programma, che è in grado di pre-generare versioni compilate in codice nativo del codice dell'applicazione, sbattendosi di più per cercare di ottimizzare.

    Dove il JIT invece ha tutte le carte in regola per vincere è sulla profiling-based optimization. Al momento di generare il codice, è importante sapere qual è il "percorso normale" del codice - ovvero, quello che prenderà nella maggior parte dei casi; ai processori moderni infatti non "piacciono" i branch condizionali (gli if, per intenderci) e il codice "sparpagliato" (i primi danno fastidio al branch predictor, i secondi all'instruction cache), per cui nel caso migliore il codice da eseguire più di frequente deve essere contiguo e con i branch disposti in una maniera specifica.

    Qui un compilatore tradizionale di base può davvero solo tirare ad indovinare (anche se esistono dei modi per dargli dei suggerimenti già da codice), cercando di fare le cosa più logica nel caso medio (una throw sarà un caso eccezionale, per cui il suo codice viene messo fuori dai piedi; il corpo di un for probabilmente in genere verrà eseguito almeno una volta; eccetera); per ovviare a questo problema i compilatori più recenti consentono di generare un eseguibile "di prova" che contiene codice che salva dati di utilizzo, da eseguire seguendo un po' di "casi tipo" di utilizzo; le informazioni generate vengono poi sfruttate alla compilazione dopo per sapere come viene effettivamente eseguito il codice e riarrangiarlo di conseguenza.

    Il JIT da questo punto di vista invece ha tutto in mano: può generare il codice instrumentato all'inizio, e quindi usare i dati delle prime esecuzioni per generare il codice ottimale per come si sta usando adesso il programma. Nel caso di linguaggi molto dinamici (Python, Lua, Javascript, ...) questo è cruciale, visto che ogni operazione sulle variabili è potenzialmente un if implicito sui loro tipi. PyPy da questo punto di vista fa delle magie incredibili, per cui prima esegue il codice in modalità interpretata per raccogliere dati, poi, quando ha capito un po' i tipi che girano, ne genera al volo una versione ottimizzata in codice nativo specializzata per i tipi e i branch effettivamente usati e manda in esecuzione questa, controllando semplicemente prima che le assunzioni sotto cui ha generato il codice siano rispettate.

    Una tecnica simile viene usata da Hotspot (il JIT della VM di Java) per capire dove perdere tempo ad ottimizzare: all'inizio genera rapidamente codice macchina non troppo ottimizzato per tutte le funzioni che vengono via via richieste, tenendo però d'occhio dove effettivamente il programma perde tanto tempo; quando ha raccolto una statistica sufficiente, inizia a ricompilare le funzioni che sono state individuate come collo di bottiglia del programma, questa volta perdendo il tempo necessario a fare un lavoro di fino (tanto ormai il resto dell'applicazione sta già girando, anche se in versione non troppo ottimizzata).

    tl;dr: il discorso è complicato (oltre che in continua evoluzione); i JIT hanno vantaggi e svantaggi potenziali, ma attualmente il C in genere tende a vincere, sia per "facilitazioni" a livello di specifiche del linguaggio, sia per minore dinamicità del linguaggio, sia per la maggiore disponibilità di tempo al momento della compilazione.
    Amaro C++, il gusto pieno dell'undefined behavior.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.