Come posso velocizzare il mio programma Perl?

Queste sono davvero due domande, ma sono così simili, e per semplificare, ho pensato di farle unire insieme:

Per la prima domanda, immagina che ti sia consegnato un progetto scritto in modo decente e che devi migliorare le prestazioni, ma non riesci a ottenere gran parte del guadagno attraverso il refactoring / l’ottimizzazione. Cosa faresti per accelerarlo in questo caso, a meno di riscriverlo in qualcosa come C?

Si prega di evitare tecniche di ottimizzazione generali a meno che non siano specifiche Perl .

L’ho chiesto prima su Python , e ho pensato che sarebbe stato utile farlo per altri linguaggi (sono particolarmente curioso se ci sono corollari per psico e pyrex per Perl).

Si prega di ricordare le regole del Club di ottimizzazione:

  1. La prima regola di Optimization Club è che tu non ottimizzi.
  2. La seconda regola di Optimization Club è che non si ottimizza senza misurare.
  3. Se l’app è in esecuzione più velocemente del protocollo di trasporto sottostante, l’ottimizzazione è finita.
  4. Un fattore alla volta.
  5. Nessun marketroids, nessun programma marketroid.
  6. I test continueranno finché dovrà.
  7. Se questa è la tua prima notte in Optimization Club, devi scrivere un caso di prova.

Quindi, supponendo che tu abbia effettivamente un codice funzionante, esegui il tuo programma sotto Devel :: NYTProf .

Trova i colli di bottiglia. Allora torna qui per dirci cosa sono.

Se non si dispone di codice funzionante, farlo funzionare prima. L’unica big ottimizzazione che tu possa mai fare è passare dal non lavorare al lavoro.

Andy ha già menzionato Devel :: NYTProf . È meraviglioso. Davvero, davvero fantastico. Usalo.

Se per qualche motivo non puoi utilizzare Devel::NYTProf , puoi Devel::NYTProf al buon vecchio Devel :: DProf , che è ormai standard con Perl da molto tempo. Se hai delle vere funzioni (in senso matematico) che impiegano molto tempo per calcolare (ad esempio, i numeri di Fibonacci), allora potresti trovare Memoize che fornisce un miglioramento della velocità.

Molte prestazioni scadenti derivano da strutture e algoritmi di dati inappropriati. Un buon corso di informatica può aiutare immensamente qui. Se hai due modi di fare le cose e vorresti confrontare le loro prestazioni, il modulo Benchmark può rivelarsi utile.

I seguenti suggerimenti Perl possono anche essere utili qui:

  • Ordinamento con confronti costosi
  • Creazione di profili con Devel :: DProf
  • Notazione Big-O e complessità algoritmica
  • Ricerca di elementi in un elenco di grandi dimensioni
  • Nozioni di base sul benchmarking
  • Memoizing

Disclaimer: Ho scritto alcune delle risorse di cui sopra, quindi potrei essere prevenuto nei loro confronti.

Ci sono molte cose su cui potresti migliorare, quindi devi prima capire cosa è lento. Altri hanno già risposto a questa domanda. Ne parlo un po ‘anche con Mastering Perl .

Un elenco incompleto di cose a cui pensare mentre stai scrivendo un nuovo codice:

  • Profili con qualcosa come Devel :: NYTProf per vedere dove trascorri la maggior parte del tuo tempo nel codice. A volte è sorprendente e facile da risolvere. Padroneggiare Perl ha molti consigli al riguardo.

  • Perl deve compilare la sorgente ogni volta e la compilazione può essere lenta. Deve trovare tutti i file e così via. Vedi, ad esempio, “A Timely Start” , di Jean-Louis Leroy, in cui accelera tutto ottimizzando le posizioni dei moduli in @INC . Se i tuoi costi di avvio sono costosi e inevitabili, potresti anche cercare perls persistenti, come pperl, mod_perl e così via.

  • Guarda alcuni dei moduli che usi. Hanno lunghe catene di dipendenze solo per fare cose semplici? Certo, non ci piace la reinvenzione, ma se la ruota che vuoi mettere sulla tua auto arriva anche con tre barche, cinque capre e un cheeseburger, magari vuoi build la tua ruota (o trovarne un’altra) .

  • Le chiamate al metodo possono essere costose. Nella suite di test Perl :: Critic, ad esempio, le sue chiamate a isa rallentano le cose. Non è qualcosa che puoi davvero evitare in tutti i casi, ma è qualcosa da tenere a mente. Qualcuno ha avuto una grande citazione che è andata in qualcosa come “Nessuna mente rinuncia a un fattore 2, è quando hai dieci persone che lo fanno che è male”. 🙂 Perl v5.22 ha alcuni miglioramenti delle prestazioni per questo.

  • Se stai chiamando gli stessi costosi metodi più e più volte ma ottieni le stesse risposte, qualcosa come Memoize potrebbe essere per te. È un proxy per la chiamata al metodo. Se è davvero una funzione (nel senso che lo stesso input fornisce lo stesso output senza effetti collaterali), non è necessario chiamarlo ripetutamente.

  • Moduli come Apache :: DBI possono riutilizzare gli handle del database per evitare la costosa apertura delle connessioni al database. È un codice davvero semplice, quindi guardare dentro può mostrarti come farlo anche se non stai usando Apache.

  • Perl non fa l’ottimizzazione della ricorsione di coda per te, quindi non venire da Lisp pensando che stai andando a fare questi algoritmi ricorsivi super veloci. Puoi trasformarli facilmente in soluzioni iterative (e ne parliamo in Intermediate Perl .

  • Guarda le tue espressioni regolari. Molti quantificatori aperti (ad esempio .* ) possono portare a un sacco di backtracking. Dai un’occhiata alle Mastering Expressions di Jeffrey Freidl per tutti i dettagli cruenti (e in diverse lingue). Controlla anche il suo sito Web regex .

  • Scopri come viene compilato il tuo Perl. Hai davvero bisogno di threading e DDEBUGGING ? Quelli ti rallentano un po ‘. Controlla l’utilità perlbench per confrontare diversi binari perl.

  • Confronta la tua applicazione con diversi Perls. Alcune versioni più recenti hanno accelerazioni, ma anche alcune versioni precedenti possono essere più veloci per serie limitate di operazioni. Non ho un consiglio particolare in quanto non so cosa stai facendo.

  • Distribuisci il lavoro. Puoi fare un lavoro asincrono in altri processi o su computer remoti? Lascia che il tuo programma funzioni su altre cose mentre qualcun altro capisce alcuni sottoproblemi. Perl ha diversi moduli asincroni e di spostamento del carico. Attenzione, però, che l’impalcatura per fare bene quella roba potrebbe perdere ogni beneficio nel farlo.

Senza dover riscrivere blocchi di grandi dimensioni, è ansible utilizzare Inline :: C per convertire qualsiasi singola subroutine lenta in C. Oppure utilizzare direttamente XS. È anche ansible convertire in modo incrementale i sottotitoli con XS. PPI / PPI :: XS fa questo, per esempio.

Ma passare a un’altra lingua è sempre l’ultima risorsa. Forse dovresti ottenere un esperto programmatore Perl per guardare il tuo codice? Più probabile che no, noterà qualche particolarità che sta seriamente danneggiando la tua performance. Oltre a questo, profila il tuo codice. Ricorda, non c’è un proiettile d’argento.

Per quanto riguarda psyco e pyrex: No, non esiste un equivalente per Perl.

Questa metà si riferisce solo alla tua domanda, ma nell’interesse della documentazione la posterò qui.

Un recente bugfix CentOS / Perl ha aumentato la velocità della nostra applicazione più di due volte. Questo è un must per chiunque usi CentOS Perl e usi le funzioni bless / overload.

Profili la tua applicazione – usando ad esempio il profiler sopra menzionato. Vedrai quindi dove sta andando il tempo

Se si impiega il tempo a fare cose diverse dall’utilizzo della CPU, è necessario ridurli prima: la CPU è facile da scalare, altre no.

Alcune operazioni sono particolarmente lente, ho trovato:

  • keys() su un hash grande è molto cattivo
  • Uso dei Data::Dumper per debug. L’utilizzo di questa funzione su una struttura di grandi dimensioni è molto lento. Evitalo se puoi. Abbiamo visto il codice che fa:

     use Data::Dumper; $debugstr = Dumper(\%bighash); if ($debugflag_mostlyoff) { log($debugstr); } 
  • La maggior parte dei moduli ha alternative con caratteristiche di performance diverse – alcune letteralmente succhiano incredibilmente male.

  • Alcune espressioni regolari possono essere molto lente (molte *. Ecc.) E possono essere sostituite da altre più veloci. Le espressioni regolari sono abbastanza facili da test unitari e test delle prestazioni (basta scrivere un programma che lo faccia in loop su un grande set di dati simulato). Le migliori espressioni regolari iniziano con qualcosa che può essere testato molto rapidamente, come una stringa letterale. A volte è meglio non cercare la cosa che stai cercando per prima, e fare un “look behind” per verificare se è davvero la cosa che stai cercando. L’ottimizzazione delle espressioni regolari è davvero un po ‘un’arte nera in cui non sono molto bravo.

Non considerare di riscrivere qualcosa in C se non come ultima risorsa. Chiamare C da Perl (o viceversa) ha un overhead relativamente grande. Se riesci a ottenere un’implementazione Perl veloce, è meglio.

Se si riscrive qualcosa in C, provare a farlo in un modo che riduca al minimo l’overhead di chiamata e le chiamate al runtime di perl (le funzioni SV *, ad esempio, copiano principalmente le stringhe in giro). Un modo per ottenere ciò è creare una funzione C che faccia di più e che la chiama meno volte. Copiare le stringhe in memoria non è bello.

D’altra parte, la riscrittura di qualcosa in C comporta un grande rischio perché è ansible introdurre nuove modalità di errore, ad esempio perdite di memoria, arresti anomali, problemi di sicurezza.

Un saggio che vale la pena di leggere sull’argomento è il discorso di Nicholas Clark. Quando perl non è abbastanza veloce (PDF). Alcuni punti sono leggermente datati, come il riferimento a Devel :: DProf, ma tieni presente che è stato scritto nel 2002.

Tuttavia, gran parte del materiale coperto rimane pertinente.

Il modo migliore per rendere il tuo programma più veloce è quello di far funzionare meno il tuo programma. Scegli l’algoritmo giusto per il lavoro. Ho visto molte applicazioni lente perché scelgono un algoritmo stupido in alcune aree del codice che viene chiamato milioni di volte. Quando esegui un milione * milione di operazioni invece di solo un milione di operazioni, il tuo programma funzionerà un milione di volte più lentamente. Letteralmente.

Ad esempio, ecco un codice che ho visto che inserisce un elemento in una lista ordinata:

 while(my $new_item = <>){ push @list, $new_item; @list = sort @list; ... use sorted list } 

sort è O (n log n). Un inserimento in una lista ordinata è O (log n).

Correggi l’algoritmo.

Le chiamate di metodo e subroutine non sono gratuite in Perl. Sono relativamente costosi. Quindi, se il tuo profilo risulta che stai spendendo una parte abbastanza grande del tempo di esecuzione in piccoli metodi di accesso, potrebbe essere un micro-ottimizzazione che vale la pena guardare.

Tuttavia, ciò che non dovresti fare è sostituire gli accessor come get_color () qui:

 package Car; # sub new {...} sub get_color { my $self = shift; return $self->{color}; } package main; #... my $color = $car->get_color(); 

con accessi diretti di incapsulamento:

 my $color = $car->{color}; 

Si potrebbe pensare che questo sia ovvio, ma si vede anche questo fatto dappertutto. Ecco cosa puoi fare, usando Class :: XSAccessor

 package Car; # sub new {...} use Class::XSAccessor getters => { get_color => 'color', }, setters => { set_color => 'color', }; 

Questo crea nuovi metodi get- e set_color () che sono implementati in XS e quindi circa il doppio della tua versione laminata a mano. Sono disponibili anche i mutatori (ad esempio “$ car-> color (‘red’)”), così come i metodi concatenati.

A seconda della tua applicazione, questo potrebbe darti una spinta molto piccola (ma essenzialmente gratuita). Non aspettarti più dell’1-2% a meno che tu non stia facendo qualcosa di particolare.

Il metodo più conveniente potrebbe essere quello di considerare l’hardware più veloce (=> architettura hardware appropriata). Non sto parlando di CPU più veloci, ma di dischi più veloci, di reti più veloci .. più veloce di qualsiasi cosa, davvero, che acceleri I / O.

L’ho sperimentato molti anni fa, quando abbiamo spostato un’applicazione basata su XML-parsing (tecnologia al tempo ) da un Windows Server (veloce e affidabile!) A una piattaforma SUN dedicata, anche se un po ‘obsoleta, con I più veloce / O tutto intorno.

Come sempre, considera

  • le prestazioni dello sviluppatore (quanto tempo ci vuole per codificare, quanto è complesso il problema, il risultato è mantenibile),
  • Prestazioni hardware,
  • Prestazioni del software

e migliorare dove la maggior parte (costo!) è efficace per il problema in questione …

Se il tuo codice ha bisogno di accelerare, è probabile che anche la tua suite di test lo faccia. Questo discorso tocca i punti chiave:

Suite di prova con carica a turbo

Scarica Perl e usa Golang. Ho cambiato il mio programma per usare Go e spead il runtime fino a 34 volte. Questo è il tempo di esecuzione di Perl.

vero 0m16.724s

Questo è il tempo Go.

vero 0m0.425s