Quali caratteristiche semantiche di Python (e di altri linguaggi dinamici) contribuiscono alla sua lentezza?


26

Non conosco molto bene Python. Sto cercando di capire più precisamente quali caratteristiche esatte dei linguaggi dinamici (à la Python, Lua, Scheme, Perl, Ruby, ....) stanno forzando le loro implementazioni ad essere lente.

Ad esempio, i macchinari metabili di Lua 5.3 renderebbero intuitivamente Lua piuttosto lento, ma in pratica si dice che Lua sia abbastanza veloce (e più veloce di Python).

Inoltre, ho l'intuizione (forse sbagliata) che, poiché sui processori attuali la memoria è molto più lenta del calcolo non elaborato (un accesso alla memoria con una cache mancata richiede lo stesso tempo di centinaia di operazioni aritmetiche), controllo dinamico del tipo (à la if (value->type != INTEGER_TAG) return;in C parlance) potrebbe funzionare abbastanza velocemente.

Naturalmente, l'intera analisi del programma (come sta facendo l'implementazione di Stalin Scheme ) può fare un'implementazione dinamica del linguaggio mentre un traduttore corre veloce, ma facciamo finta di non avere il tempo di progettare inizialmente un intero analizzatore di programmi.

(Sto progettando un linguaggio dinamico nel mio monitor MELT , e parte di esso verrebbe tradotto in C)



1
Lua Performance Tips , che spiega perché alcuni programmi Lua sono lenti e come risolverli.
Robert Harvey,

Risposte:


24

Quali caratteristiche semantiche di Python (e di altri linguaggi dinamici) contribuiscono alla sua lentezza?

Nessuna.

Le prestazioni delle implementazioni linguistiche sono una funzione del denaro, delle risorse e delle tesi di dottorato, non delle caratteristiche linguistiche. Self è molto più dinamico di Smalltalk e leggermente più dinamico di Python, Ruby, ECMAScript o Lua e aveva una VM che superava tutte le VM Lisp e Smalltalk esistenti (in effetti, la distribuzione di Self veniva fornita con un piccolo interprete Smalltalk scritto in Self e anche quello era più veloce della maggior parte delle VM Smalltalk esistenti), ed era competitivo, e talvolta anche più veloce delle implementazioni C ++ dell'epoca.

Quindi, Sun ha smesso di finanziare Self e IBM, Microsoft, Intel e Co. hanno iniziato a finanziare C ++ e la tendenza si è invertita. Gli sviluppatori Self hanno lasciato Sun per avviare la propria azienda, dove hanno utilizzato la tecnologia sviluppata per Self VM per costruire una delle VM Smalltalk più veloci di sempre (la VM animorfa), quindi Sun ha riacquistato quella società e una versione leggermente modificata di che Smalltalk VM è ora meglio conosciuto con il nome di "HotSpot JVM". Ironia della sorte, i programmatori Java guardano dall'alto in basso i linguaggi dinamici per essere "lenti", quando in realtà Javaè stato lento fino all'adozione della tecnologia del linguaggio dinamico. (Sì, esatto: HotSpot JVM è essenzialmente una VM Smalltalk. Il verificatore bytecode esegue molti controlli del tipo, ma una volta che il bytecode è accettato dal verificatore, dalla VM e, in particolare, dall'ottimizzatore e dal JIT non lo è molto interessante con i tipi statici!)

CPython semplicemente non fa molte cose che rendono veloci i linguaggi dinamici (o piuttosto il dispacciamento dinamico): compilazione dinamica (JIT), ottimizzazione dinamica, allineamento speculativo, ottimizzazione adattiva, de-ottimizzazione dinamica, feedback / inferenza di tipo dinamico. C'è anche il problema che quasi tutta la libreria standard e core è scritta in C, il che significa che anche se rendi Python 100 volte più veloce all'improvviso, non ti aiuterà molto, perché qualcosa come il 95% del codice eseguito da un Il programma Python è C, non Python. Se tutto fosse scritto in Python, anche un moderato aumento di velocità creerebbe un effetto valanghe, in cui gli algoritmi diventano più veloci e le strutture dei dati core diventano più veloci, ma ovviamente anche le strutture dei dati core vengono utilizzate all'interno degli algoritmi e gli algoritmi core e i dati core le strutture sono utilizzate ovunque,

Ci sono un paio di cose notoriamente dannose per i linguaggi OO gestiti in memoria (dinamici o meno) nei sistemi odierni. La memoria virtuale e la protezione della memoria possono essere un killer per le prestazioni della garbage collection in particolare e per le prestazioni del sistema in generale. Ed è completamente inutile in un linguaggio sicuro per la memoria: perché proteggere dagli accessi alla memoria illegali quando non ci sono accessi alla memoria nella lingua per cominciare? Azul ha capito di usare MMU potenti e moderni (Intel Nehalem e più recenti, e l'equivalente di AMD) per aiutare la raccolta dei rifiuti invece di ostacolarli, ma anche se è supportato dalla CPU, gli attuali sottosistemi di memoria dei sistemi operativi tradizionali non sono abbastanza potenti per consentire questo (motivo per cui la JVM di Azul in realtà funziona virtualizzata sul metallo nudo oltre il sistema operativo, non al suo interno).

Nel progetto Singularity OS, Microsoft ha misurato un impatto del ~ 30% sulle prestazioni del sistema quando si utilizza la protezione MMU anziché il sistema di tipi per la separazione dei processi.

Un'altra cosa che Azul notò quando costruirono le loro CPU Java specializzate fu che le moderne CPU tradizionali si concentrano sulla cosa completamente sbagliata quando provano a ridurre il costo dei fallimenti della cache: provano a ridurre il numero di errori della cache attraverso cose come la previsione del ramo, il prefetch della memoria, e così via. Ma, in un programma OO fortemente polimorfico, i modelli di accesso sono sostanzialmente pseudo-casuali, semplicemente non c'è nulla da prevedere. Quindi, tutti questi transistor sono semplicemente sprecati e ciò che si dovrebbe fare è invece ridurre il costo di ogni singola perdita della cache. (Il costo totale è il costo #misses *, il mainstream cerca di far cadere il primo, Azul il secondo.) Gli Java Compute Accelerators di Azul potrebbero avere 20000 missioni simultanee della cache in volo e fare comunque progressi.

Quando ha iniziato Azul, che pensavano avrebbero preso alcuni componenti off-the-shelf di I / O e progettare il proprio core della CPU specializzato, ma quello che in realtà ha finito per avere bisogno di fare era l'esatto contrario: hanno preso un piuttosto standard di off-the- core RISC core a 3 indirizzi e progettato il proprio controller di memoria, MMU e sottosistema cache.

tl; dr : La "lentezza" di Python non è una proprietà del linguaggio ma a) la sua ingenua (primaria) implementazione eb) il fatto che CPU e sistemi operativi moderni sono specificamente progettati per far funzionare C velocemente e le funzionalità che avere per C non sta aiutando (cache) o addirittura danneggiando attivamente (memoria virtuale) le prestazioni di Python.

E puoi inserire praticamente qualsiasi linguaggio gestito dalla memoria con un polimorfismo dinamico ad hoc qui ... quando si tratta delle sfide di un'implementazione efficiente, anche Python e Java sono praticamente "lo stesso linguaggio".


Hai un link o un riferimento per Azul?
Basile Starynkevitch l'

4
Non sono d'accordo sul fatto che la semantica di una lingua non abbia alcun effetto sulla sua capacità di essere implementata in modo efficiente. Sì, una buona implementazione JIT con ottimizzazioni e analisi di prima classe può apportare un notevole miglioramento alle prestazioni di un linguaggio, ma alla fine ci sono alcuni aspetti della semantica che inevitabilmente finiranno per essere colli di bottiglia. Che si tratti del requisito di C per un aliasing rigoroso dei puntatori o del requisito di Python che le operazioni dell'elenco vengano eseguite atomicamente, ci sono alcune decisioni semantiche che inevitabilmente finiscono per danneggiare le prestazioni di alcune applicazioni.
Jules l'

1
A parte questo, hai un riferimento per quel miglioramento del 30% per Singolarità? Sono stato un sostenitore dei sistemi operativi basati sulla protezione della lingua per molti anni, ma non ho mai visto quella cifra prima e la trovo abbastanza sorprendente (le cifre che ho visto in passato sono state più vicine al 10%) e mi chiedo cosa hanno fatto così tanto miglioramento ...
Jules l'

5
@MasonWheeler: Perché ci sono solo pessime implementazioni Python là fuori. Nessun implementatore Python ha speso nemmeno una piccola parte del denaro, delle persone, della ricerca e delle risorse che IBM, Sun, Oracle, Google e Co. hanno speso per J9, JRockit, HotSpot e Co. Tutte e 5 le implementazioni di Python combinate probabilmente non lo fanno nemmeno avere la forza lavoro che Oracle sta spendendo solo per il garbage collector. IBM sta lavorando su un'implementazione di Python basata su Eclipse OMR (il framework di macchine virtuali open source componentizzato estratto da J9), sono disposto a scommettere che le sue prestazioni saranno ben entro un ordine di grandezza di J9
Jörg W Mittag

2
Per la cronaca, C è lento rispetto a Fortran per il lavoro numerico poiché Fortran applica un aliasing rigoroso in modo che l'ottimizzatore possa essere più aggressivo.
Michael Shops nell'8

8

Mentre l'implementazione attuale di Python (che manca di molte ottimizzazioni eseguite da altri linguaggi dinamici, ad esempio implementazioni Javascript moderne e, come fai notare, Lua) è una fonte della maggior parte dei suoi problemi, ha alcuni problemi semantici che la renderebbero difficile per un'implementazione competere con altre lingue, almeno in certi settori. Alcuni che vale la pena considerare in particolare:

  • Le operazioni di elenco e dizionario sono richieste dalla definizione della lingua per essere atomiche. Ciò significa che, a meno che un compilatore JIT non sia in grado di dimostrare che nessun riferimento a un oggetto elenco è sfuggito al thread corrente (un'analisi che è difficile in molti casi e impossibile nel caso generale), deve garantire che l'accesso all'oggetto sia serializzato (ad es. tramite blocco). L'implementazione di CPython risolve questo problema utilizzando il noto "blocco dell'interprete globale" che impedisce che il codice Python venga effettivamente utilizzato in ambienti multiprocessore con tecniche multi-thread e, mentre sono possibili altre soluzioni, tutte hanno problemi di prestazioni.

  • Python non ha alcun meccanismo per specificare l'uso di oggetti valore; tutto viene gestito per riferimento, aggiungendo ulteriore direzione indiretta laddove non sia necessariamente necessario. Mentre è possibile per un compilatore JIT inferire oggetti valore in alcuni casi e ottimizzarlo automaticamente, non è possibile farlo in generale e quindi codice che non è scritto con cura per garantire l'ottimizzazione è possibile (che è in qualche modo un'arte nera) soffrirà.

  • Python ha una evalfunzione, il che significa che un compilatore JIT non può fare ipotesi su azioni che non si verificano, anche se esegue un'analisi dell'intero programma, purché evalvenga usato una volta. Ad esempio, un compilatore Python non può presumere che una classe non abbia sottoclassi e quindi devirtualizzare le chiamate al metodo, poiché tale presupposto potrebbe essere successivamente negato tramite una chiamata a eval. Deve invece eseguire controlli dinamici del tipo per assicurarsi che le assunzioni fatte dal codice compilato nativamente non siano state invalidate prima dell'esecuzione di quel codice.


3
Quest'ultimo punto può essere mitigato evalinnescando una ricompilazione e / o de-ottimizzazione.
Jörg W Mittag,

4
A proposito, neanche questo è unico per Python. Java (o meglio la JVM) ha un caricamento dinamico del codice e un collegamento dinamico, quindi l'analisi della gerarchia delle classi equivale a risolvere anche il problema dell'arresto. Tuttavia, HotSpot incorpora felicemente i metodi polimorfici e, se qualcosa nella gerarchia di classi cambia, beh, li disinserirà di nuovo.
Jörg W Mittag,
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.