Perché dobbiamo scambiare l'astrazione per la velocità?


11

Perché le lingue di alto livello non sembrano mai raggiungere le lingue di livello inferiore in termini di velocità? Esempi di linguaggi di alto livello sarebbero Python, Haskell e Java. Le lingue di basso livello sarebbero più difficili da definire, ma diciamo che C. I confronti si possono trovare in tutto Internet 1 e tutti concordano sul fatto che C è molto più veloce, a volte di un fattore 10 o più.1

Cos'è che causa una così grande differenza nelle prestazioni e perché le lingue di alto livello non riescono a raggiungere?

Inizialmente credevo che fosse tutta colpa dei compilatori e che le cose miglioreranno in futuro, ma alcuni dei linguaggi di livello superiore più popolari sono in circolazione da decenni e sono ancora in ritardo quando si tratta di velocità. Non possono semplicemente compilare un simile albero di sintassi C e quindi seguire le stesse procedure che generano il codice macchina? O forse ha a che fare con la sintassi stessa?


esempi:1


5
"e tutti concordano sul fatto che C è molto più veloce" - questo è certamente sbagliato.
Raffaello

2
Ad ogni modo, penso che il mio post risponda meglio alla mia risposta a una domanda altrettanto mal concepita ; duplicare?
Raffaello

2
Vedi anche qui e qui . Fai attenzione alle risposte indesiderate.
Raffaello

Rilevante: stackoverflow.com/questions/6964392/… Il mito di tutte le "lingue di alto livello" che sono lente è abbastanza triste.
xji,

Sono d'accordo con gli altri che l'astrazione! = Lentezza, ma temo che ci sia un problema molto più grande di cui gli insegnanti di informatica (ero uno) non sono a conoscenza. Ciò significa che per i programmi reali (1000 righe di codice e oltre) ci sono molti modi per eseguirli e quelli possono differire nelle prestazioni in base agli ordini di grandezza. Il solo fatto di pensare a big-O non riesce assolutamente a capire. Controllare qui .
Mike Dunlavey,

Risposte:


19

Sfatare alcuni miti

  1. Non esiste una lingua veloce. Una lingua può generalmente produrre codice veloce, ma lingue diverse eccelleranno in diversi benchmark. Siamo in grado di classificare le lingue su una particolare serie di parametri errati, ma non possiamo classificare le lingue nel vuoto.

  2. Il codice C tende ad essere più veloce perché le persone che hanno bisogno di ogni centimetro di prestazioni usano C. Una statistica secondo cui C è più veloce "di un fattore 10" potrebbe essere inaccurata, perché potrebbe essere che le persone che usano Python non si preoccupino tanto sulla velocità e non ha scritto il codice Python ottimale. Lo vediamo in particolare con lingue come Haskell. Se ci provi davvero , puoi scrivere Haskell che si esibisce alla pari con C. Ma la maggior parte delle persone non ha bisogno di quella performance, quindi abbiamo un sacco di confronti imperfetti.

  3. A volte, è la sicurezza, non l'astrazione, che rende C veloce. La mancanza di limiti di array e controlli puntatore null consente di risparmiare tempo e sono stati la causa di numerose falle di sicurezza nel corso degli anni.

  4. Le lingue non sono veloci, le implementazioni sono veloci. Molti linguaggi astratti iniziano lentamente, perché la velocità non è il loro obiettivo, ma diventano più veloci man mano che vengono aggiunte sempre più ottimizzazioni.

L'astrazione rispetto al compromesso di velocità è forse imprecisa. Suggerirei un confronto migliore:

Semplicità, velocità, astrazione: scegli due.

Se eseguiamo algoritmi identici in lingue diverse, la questione della velocità si riduce al problema di "Quanta roba dobbiamo fare in fase di esecuzione per farlo funzionare?"

In un linguaggio estremamente astratto che è semplice , come Python o JavaScript, perché non conosciamo la rappresentazione dei dati fino al runtime, ci sono molti riferimenti ai puntatori e controlli dinamici che avvengono in fase di runtime, che sono lenti.

Allo stesso modo, ci sono molti controlli che devono essere fatti per assicurarsi di non rovinare il computer. Quando chiami una funzione in Python, devi assicurarti che l'oggetto che stai chiamando sia effettivamente una funzione, poiché altrimenti potresti finire per eseguire codice casuale che fa cose terribili.

Infine, la maggior parte dei linguaggi astratti ha un sovraccarico dalla raccolta dei rifiuti. La maggior parte dei programmatori ha concordato sul fatto che dover tenere traccia delle vite della memoria allocata in modo dinamico è una seccatura, e preferirebbero che un garbage collector lo faccia per loro in fase di esecuzione. Questo richiede tempo, che un programma C non deve spendere per GC.

Astratto Non significa lento

Tuttavia, ci sono lingue che sono sia astratte che veloci. Il più dominante in questa era è la ruggine. Introducendo un correttore di prestito e un sofisticato sistema di tipi, consente il codice astratto e utilizza le informazioni in fase di compilazione per ridurre la quantità di lavoro che dobbiamo eseguire in fase di esecuzione (ad esempio, garbage collection).

Qualsiasi linguaggio tipizzato staticamente ci fa risparmiare tempo riducendo il numero di controlli di runtime e introduce complessità richiedendoci di compiacere un programmatore al momento della compilazione. Allo stesso modo, se abbiamo un linguaggio che codifica se un valore può essere nullo nel suo sistema di tipi, possiamo risparmiare tempo con i controlli null-pointer in fase di compilazione.


2
"Il codice C tende ad essere più veloce perché le persone che hanno bisogno di ogni pollice di prestazioni usano C" - esattamente. Voglio vedere i parametri di riferimento del modulo "tempo medio di esecuzione del codice scritto da studenti / professionisti X anni con Y anni di esperienza in Z". Mi aspetto che la risposta per C sia di solito "codice non corretto" per X e Y piccoli. Sarebbe davvero interessante vedere quanta più esperienza / competenza è necessaria per sfruttare il potenziale delle prestazioni C promesse.
Raffaello

Haskell è davvero un'eccezione che dimostra la regola. ;) Penso che C ++ abbia trovato una via di mezzo piuttosto ragionevole su GC dai suoi puntatori intelligenti fintanto che non annidate i puntatori condivisi e sarà veloce come C.
Kaveh

0

ecco alcune idee chiave su questo.

  • un semplice confronto / caso di studio per rendere l'astrazione wrt vs la velocità è java vs c ++. java è stato progettato per sottrarre alcuni degli aspetti di livello inferiore di c ++ come la gestione della memoria. nei primi tempi (intorno all'invenzione della lingua a metà degli anni '90), il rilevamento di java garbage non è stato molto veloce, ma dopo alcuni decenni di ricerca, i garbage collector sono estremamente ottimizzati / veloci / ottimizzati, quindi i garbage collector sono in gran parte eliminati come scarico delle prestazioni su Java. ad esempio vedere anche questo titolo del 1998: i test delle prestazioni mostrano Java veloce come C ++ / javaworld

  • i linguaggi di programmazione e la loro lunga evoluzione hanno una "struttura piramidale / gerarchica" intrinseca come una sorta di modello di progettazione trascendente. nella parte superiore della piramide c'è qualcosa che controlla le altre sezioni inferiori della piramide. in altre parole, i mattoni sono fatti di mattoni. questo può essere visto anche nella struttura dell'API. in questo senso, una maggiore astrazione porta sempre ad un nuovo componente nella parte superiore della piramide che controlla altri componenti. quindi, in un certo senso, non è tanto che tutte le lingue sono a livello l'una con l'altra, ma che le lingue invocano routine in altre lingue. ad esempio molti linguaggi di scripting (python / ruby) spesso chiamano librerie C o C ++, le routine numeriche o matriciali ne sono un tipico esempio. quindi ci sono lingue di livello superiore e inferiore e le lingue di alto livello chiamano lingue di livello inferiore per così dire. in questo senso la misurazione della velocità relativa non è realmente comparabile.

  • si potrebbe dire che vengono continuamente inventate nuove lingue per cercare di ottimizzare il compromesso di astrazione / velocità, ovvero il suo obiettivo progettuale chiave. forse non è tanto che una maggiore astrazione sacrifica sempre la velocità ma che si cerca sempre un equilibrio migliore con i nuovi progetti. ad esempio, Google Go è stato ottimizzato in molti modi specificamente tenendo presente il compromesso, per essere contemporaneamente sia di alto livello che performanti. vedi ad esempio Google Go: perché il linguaggio di programmazione di Google può competere con Java nel mondo enterprise / tech


0

Il modo in cui mi piace pensare alle prestazioni è "dove la gomma incontra la strada". Il computer esegue le istruzioni, non le astrazioni.

Quello che voglio vedere è questo: ogni istruzione che viene eseguita "guadagna il suo mantenimento" contribuendo sostanzialmente al risultato finale? Come esempio troppo semplice, considera di cercare una voce in una tabella di 1024 voci. Questo è un problema di 10 bit perché il programma deve "imparare" 10 bit prima di conoscere la risposta. Se l'algoritmo è la ricerca binaria, ogni iterazione fornisce 1 bit di informazione, perché riduce l'incertezza di un fattore 2. Quindi ci vogliono 10 iterazioni, una per ogni bit.

La ricerca lineare, invece, inizialmente è molto inefficiente perché le prime iterazioni riducono l'incertezza di un fattore molto piccolo. Quindi non stanno imparando molto per lo sforzo speso.

OK, quindi se il compilatore può consentire all'utente di racchiudere buone istruzioni in un modo considerato "astratto", va bene.


0

Per sua natura, l'astrazione riduce la comunicazione di informazioni, sia al programmatore che ai livelli inferiori del sistema (compilatore, librerie e sistema di runtime). A favore dell'astrazione, ciò generalmente consente agli strati inferiori di supporre che il programmatore non si occupi di alcun comportamento non specificato, offrendo una maggiore flessibilità nel fornire il comportamento specificato.

Un esempio di potenziale beneficio da questo aspetto "non preoccuparti" è nel layout dei dati. In C (bassa astrazione), il compilatore è più vincolato nelle ottimizzazioni del layout dei dati. Anche se il compilatore è in grado di discernere (ad esempio, attraverso le informazioni del profilo) che le ottimizzazioni hot / cold o false-sharing-elusione sarebbero utili, in genere viene impedito di applicarle. (Esiste una certa libertà nel specificare "come se", cioè nel trattare le specifiche in modo più astratto, ma derivare tutti i potenziali effetti collaterali aggiunge un onere al compilatore.)

Una specifica più astratta è anche più robusta contro i cambiamenti nei compromessi e negli usi. I livelli inferiori sono meno vincolati nel riaprimizzare il programma per nuove caratteristiche del sistema o nuovi usi. Una specifica più concreta deve essere riscritta da un programmatore oppure gli strati inferiori devono fare uno sforzo aggiuntivo per garantire un comportamento "come se".

L'aspetto che ostacola le prestazioni delle informazioni che nascondono l'astrazione è "impossibile esprimere", che i livelli inferiori gestiranno in genere come "non lo so". Ciò significa che i livelli inferiori devono discernere le informazioni utili per l'ottimizzazione da altri mezzi come l'uso generale tipico, l'uso mirato o informazioni specifiche sul profilo.

L'impatto del nascondere le informazioni funziona anche nell'altra direzione. Il programmatore può essere più produttivo non dovendo considerare e specificare ogni dettaglio, ma il programmatore potrebbe avere meno informazioni sull'impatto delle scelte di progettazione di livello superiore.

D'altra parte, quando il codice è più specifico (meno astratto), i livelli inferiori del sistema possono semplicemente fare ciò che viene loro detto di fare mentre gli viene detto di farlo. Se il codice è ben scritto per l'uso mirato, si adatterà bene all'uso mirato. Un linguaggio meno astratto (o paradigma di programmazione) consente al programmatore di ottimizzare l'implementazione mediante una progettazione dettagliata e l'uso di informazioni che non sono facilmente comunicate in un determinato linguaggio agli strati inferiori.

Come è stato notato, i linguaggi (o le tecniche di programmazione) meno astratti sono interessanti quando ulteriori abilità e sforzi del programmatore possono produrre risultati utili. Quando si applicano maggiori sforzi e abilità del programmatore, i risultati saranno in genere migliori. Inoltre, un sistema linguistico che viene utilizzato meno per le applicazioni critiche per le prestazioni (enfatizzando invece lo sforzo di sviluppo o l'affidabilità - i controlli dei limiti e la raccolta dei rifiuti non riguardano solo la produttività del programmatore ma anche la correttezza, l'astrazione che riduce il carico mentale sul programmatore può migliorare l'affidabilità) avrà meno pressione per migliorare le prestazioni.

La specificità funziona anche contro il principio di non ripetere te stesso perché l'ottimizzazione è in genere possibile adattando il codice ad un uso specifico. Ciò ha evidenti implicazioni in termini di affidabilità e sforzo di programmazione.

Le astrazioni fornite da una lingua possono anche includere un lavoro indesiderato o non necessario senza mezzi per scegliere un'astrazione meno pesante. Mentre il lavoro non necessario a volte può essere scoperto e rimosso dai livelli inferiori (ad esempio, i controlli dei limiti possono essere estratti dal corpo di un ciclo e completamente rimossi in alcuni casi), determinare che si tratta di un'ottimizzazione valida richiede più "abilità e sforzo" da il compilatore.

L'età e la popolarità della lingua sono anche fattori importanti sia per la disponibilità di programmatori qualificati sia per la qualità dei livelli inferiori del sistema (incluse librerie mature ed esempi di codice).

Un altro fattore di conflitto in tali confronti è la differenza in qualche modo ortogonale tra compilazione anticipata e compilazione just-in-time. Mentre la compilazione just-in-time può sfruttare più facilmente le informazioni del profilo (non fare affidamento sul programmatore per fornire esecuzioni del profilo) e l'ottimizzazione specifica del sistema (la compilazione anticipata può mirare a una più ampia compatibilità), l'overhead dell'ottimizzazione aggressiva viene considerato come parte delle prestazioni di runtime. I risultati JIT possono essere memorizzati nella cache, riducendo il sovraccarico per il codice comunemente usato. (L'alternativa della ripopimizzazione binaria può fornire alcuni vantaggi della compilazione JIT, ma i formati di distribuzione binaria tradizionali rilasciano la maggior parte delle informazioni sul codice sorgente, costringendo potenzialmente il sistema a tentare di discernere l'intento da un'implementazione specifica.)

(I linguaggi di astrazione più bassi, a causa della loro enfasi sul controllo del programmatore, favoriscono l'uso della compilazione anticipata. La compilazione in fase di installazione potrebbe essere tollerata, sebbene la selezione dell'implementazione in fase di collegamento fornirebbe un maggiore controllo del programmatore. La compilazione JIT sacrifica il controllo significativo. )

C'è anche il problema della metodologia di benchmarking. Pari sforzi / abilità sono effettivamente impossibili da stabilire, ma anche se si potessero raggiungere tali obiettivi linguistici influenzerebbe i risultati. Se fosse richiesto un tempo di programmazione massimo basso, un programma per un linguaggio meno astratto potrebbe non riuscire nemmeno a essere completamente scritto rispetto a una semplice espressione idiomatica in un linguaggio più astratto. Se fosse consentito un tempo / sforzo di programmazione massimo elevato, i linguaggi a bassa astrazione avrebbero un vantaggio. I parametri di riferimento che presentano i migliori risultati sarebbero naturalmente distorti a favore di linguaggi meno astratti.

Talvolta è possibile programmare in una lingua meno idiomatica in una lingua per ottenere i vantaggi di altri paradigmi di programmazione, ma anche quando è disponibile il potere espressivo i compromessi per farlo potrebbero non essere favorevoli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.