Perché Java non ha affatto l'ottimizzazione per la ricorsione della coda?


92

Da quello che ho letto: il motivo è che non è facile determinare quale metodo verrà effettivamente chiamato in quanto ereditarietà.

Tuttavia, perché Java non ha almeno l'ottimizzazione della ricorsione della coda per i metodi statici e impone il modo corretto di chiamare i metodi statici con il compilatore?

Perché Java non ha alcun supporto per la ricorsione della coda?

Non sono sicuro che ci sia qualche difficoltà qui.


Per quanto riguarda il duplicato suggerito , come spiegato da Jörg W Mittag 1 :

  • L'altra domanda sul TCO, questa su TRE. TRE è molto più semplice di TCO.
  • Inoltre, l'altra domanda pone quali limiti la JVM impone alle implementazioni del linguaggio che desiderano compilare nella JVM, questa domanda si pone su Java, che è l'unica lingua che non è limitata dalla JVM, poiché le specifiche JVM possono essere modificate da le stesse persone che progettano Java.
  • Infine, non vi è nemmeno una limitazione nella JVM su TRE, poiché la JVM ha GOTO intra-metodo, che è tutto ciò che è necessario per TRE

1 Formattazione aggiunta per richiamare i punti realizzati.


26
Per citare Eric Lippert : "Le funzionalità non sono economiche; sono estremamente costose e non devono solo giustificare il proprio costo, devono anche giustificare il costo opportunità di non fare le altre cento funzionalità che avremmo potuto fare con quel budget". Java è stato progettato in parte per attirare gli sviluppatori C / C ++ e l'ottimizzazione della ricorsione della coda non è garantita in tali lingue.
Doval,

5
Correggimi se sbaglio, ma Eric Lippert è il designer di C # che ha l'ottimizzazione della ricorsione della coda?
Informato il

1
Fa parte del team di compilatori C #, sì.
Doval,

10
Da quello che ho capito, la SIC potrebbe farlo , se certe condizioni fossero soddisfatte , forse. Quindi in pratica non puoi fare affidamento su di esso. Ma se C # ce l'abbia o no non ha importanza per il punto di Eric.
Doval,

4
@InstructedA Se scavi un po 'più a fondo, puoi vedere che non è mai stato fatto nel compilatore JIT a 32 bit. Il JIT a 64 bit è più recente e più intelligente in molti modi. Il compilatore sperimentale ancora più recente (sia a 32 che a 64 bit) è ancora più intelligente e supporterà l'ottimizzazione della ricorsione della coda in IL che non lo richiede esplicitamente. C'è un altro punto che devi prendere in considerazione: i compilatori JIT non hanno molto tempo. Sono fortemente ottimizzati per la velocità - l'applicazione che potrebbe richiedere ore per essere compilata in C ++ dovrà comunque passare da IL a nativo in un paio di centinaia di ms, al massimo (almeno in parte).
Luaan,

Risposte:


132

Come spiegato da Brian Goetz (Java Language Architect presso Oracle) in questo video :

nelle classi jdk [...] ci sono una serie di metodi sensibili alla sicurezza che si basano sul conteggio dei frame dello stack tra il codice della libreria jdk e il codice chiamante per capire chi li sta chiamando.

Tutto ciò che ha cambiato il numero di fotogrammi nello stack potrebbe rompere questo e causare un errore. Ammette che questa è stata una ragione stupida, e da allora gli sviluppatori JDK hanno sostituito questo meccanismo.

Inoltre menziona che non è una priorità, ma quella ricorsione della coda

finirà per farlo.

NB Questo vale per HotSpot e OpenJDK, altre VM possono variare.


7
Sono sorpreso che ci sia una risposta così semplice e secca come questa! Ma sembra davvero La risposta - è possibile ora, per motivi tecnici obsoleti che non è già stato fatto, e quindi ora aspettiamo solo che qualcuno decida che è abbastanza importante da implementare.
BrianH,

1
Perché non implementare una soluzione alternativa? Come un'etichetta sotto copertura che passa appena all'inizio della chiamata del metodo con nuovi valori per gli argomenti? Questa sarebbe un'ottimizzazione in fase di compilazione e non avrebbe bisogno di spostare i frame dello stack o causare violazioni della sicurezza.
James Watkins,

2
Sarebbe molto meglio per noi se tu o qualcun altro qui puoi scavare più a fondo e fornire in modo specifico quali siano quei "metodi sensibili alla sicurezza". Grazie!
Informato il

3
@InstructedA - vedi securingjava.com/chapter-three/chapter-three-6.html che contiene una descrizione dettagliata di come il sistema Java Security Manager ha funzionato circa il rilascio di Java 2.
Jules

3
Una soluzione alternativa consiste nell'utilizzare un altro linguaggio JVM. Scala, per esempio, lo fa nel compilatore, non in fase di esecuzione.
James Moore,

24

Java non ha l'ottimizzazione delle chiamate di coda per lo stesso motivo per cui la maggior parte delle lingue imperative non ce l'hanno. I loop imperativi sono lo stile preferito della lingua e il programmatore può sostituire la ricorsione della coda con i loop imperativi. La complessità non vale la pena per una funzionalità il cui uso è scoraggiato come una questione di stile.

Questa cosa in cui i programmatori vogliono talvolta scrivere in stile FP in linguaggi altrimenti imperativi è entrata in voga solo negli ultimi 10 anni circa, dopo che i computer hanno iniziato a ridimensionare i core anziché i GHz. Anche adesso, non è così popolare. Se suggerissi di sostituire un ciclo imperativo con la ricorsione della coda al lavoro, metà dei revisori del codice riderebbero e l'altra metà darebbe uno sguardo confuso. Anche nella programmazione funzionale, generalmente eviti la ricorsione della coda a meno che altri costrutti come le funzioni di ordine superiore non si adattino perfettamente.


36
Questa risposta non sembra giusta. Solo perché la ricorsione della coda non è strettamente necessaria non significa che non sarebbe utile. Le soluzioni ricorsive sono spesso più facili da comprendere dell'iterazione, ma la mancanza di chiamate di coda significa che gli algoritmi ricorsivi diventano errati per problemi di grandi dimensioni che farebbero impazzire. Si tratta di correttezza, non di prestazioni (spesso vale la pena scambiare velocità per semplicità). Come osserva la risposta corretta, la mancanza di chiamate di coda è dovuta a uno strano modello di sicurezza che si basa sulle tracce dello stack, non sul bigottismo imperativo.
Amon,

4
@amon James Gosling una volta disse che non avrebbe aggiunto una funzionalità a Java se non fosse stata richiesta da più persone , e solo allora l'avrebbe presa in considerazione . Quindi non sarei sorpreso se parte della risposta fosse davvero "potresti sempre usare un forloop" (e allo stesso modo per funzioni di prima classe contro oggetti). Non arriverei al punto di chiamarlo "fanatismo imperativo", ma non credo che sarebbe stato molto richiesto nel 1995 quando la preoccupazione principale era probabilmente la velocità di Java e la mancanza di generici.
Doval,

7
@amon, un modello di sicurezza mi sembra una ragione legittima per non aggiungere il TCO a una lingua preesistente, ma una ragione scadente per non progettarlo nella lingua in primo luogo. Non è possibile lanciare una funzione principale visibile al programmatore per includere una funzione secondaria dietro le quinte. "Perché Java 8 non ha il TCO" è una domanda molto diversa da "Perché Java 1.0 non ha il TCO?" Stavo rispondendo a quest'ultimo.
Karl Bielefeldt,

3
@rwong, puoi trasformare qualsiasi funzione ricorsiva in una iterativa. (Se posso, ho scritto un esempio qui )
alain

3
@ZanLynx dipende dal tipo di problema. Ad esempio, il modello di visitatore può beneficiare del TCO (a seconda delle specifiche della struttura e del visitatore) pur non avendo nulla a che fare con FP. Passare attraverso le macchine a stati è simile sebbene la trasformazione usando i trampolini sembri leggermente più naturale (a seconda del problema). Anche se, tecnicamente, puoi implementare alberi usando i loop, credo che il consenso sia che la ricorsione sia molto più naturale (simile per DFS anche se in questo caso potresti evitare la ricorsione a causa di limiti di stack anche con TCO).
Maciej Piechotka,

5

Java non ha un'ottimizzazione delle chiamate elevata perché JVM non ha un bytecode per le chiamate di coda (a un puntatore di funzione staticamente sconosciuto , ad esempio un metodo in alcune vtable).

Sembra per ragioni sociali (e forse tecniche), l'aggiunta di una nuova operazione bytecode nella JVM (che renderebbe incompatibile con le versioni precedenti di quella JVM) è terribilmente difficile per il proprietario delle specifiche JVM.

Le ragioni tecniche per non aggiungere un nuovo bytecode nelle specifiche JVM includono il fatto che le implementazioni JVM nella vita reale sono parti di software terribilmente complesse (ad esempio a causa delle molte ottimizzazioni JIT che sta facendo).

Le chiamate di coda per alcune funzioni sconosciute richiedono la sostituzione del frame dello stack corrente con uno nuovo e tale operazione dovrebbe trovarsi nella JVM (non si tratta solo di modificare il compilatore che genera il bytecode).


17
La domanda non riguarda le chiamate di coda, si tratta di ricorsione della coda. E la domanda non riguarda il linguaggio bytecode JVM, si tratta del linguaggio di programmazione Java, che è un linguaggio completamente diverso. Scala compila il bytecode JVM (tra gli altri) e ha l'eliminazione della ricorsione della coda. Le implementazioni dello schema sulla JVM hanno chiamate di coda adeguate e complete. Java 7 (JVM Spec, 3a edizione) ha aggiunto un nuovo bytecode. IBM J9 JVM esegue il TCO anche senza la necessità di un bytecode speciale.
Jörg W Mittag,

1
@JörgWMittag: è vero, ma poi mi chiedo se è proprio vero che il linguaggio di programmazione Java non ha chiamate di coda adeguate. Potrebbe essere più preciso affermare che il linguaggio di programmazione Java non deve avere chiamate di coda adeguate, in quanto nulla nelle specifiche lo impone. (Cioè: non sono sicuro che qualcosa nelle specifiche in realtà vieti un'implementazione dall'eliminazione delle chiamate di coda. Semplicemente non è menzionato.)
Ruakh

4

A meno che una lingua abbia una sintassi speciale per effettuare una chiamata di coda (ricorsiva o meno) e un compilatore squawk quando viene richiesta una chiamata di coda ma non può essere generata, l'ottimizzazione "opzionale" di coda o ricorsione di coda produrrà situazioni in cui un pezzo di codice può richiedere meno di 100 byte di stack su una macchina, ma più di 100.000.000 di byte di stack su un'altra. Tale differenza dovrebbe essere considerata qualitativa piuttosto che meramente quantitativa.

Si prevede che le macchine possano avere dimensioni dello stack diverse, quindi è sempre possibile che il codice funzioni su una macchina ma esploda su un'altra. Generalmente, tuttavia, il codice che funzionerà su una macchina anche quando lo stack è costretto artificialmente probabilmente funzionerà su tutte le macchine con dimensioni dello stack "normali". Se, tuttavia, un metodo che ricorre a 1.000.000 di profondità è ottimizzato per la coda su una macchina ma non su un'altra, l'esecuzione sulla prima macchina probabilmente funzionerà anche se il suo stack è insolitamente piccolo e fallisce su quest'ultimo anche se il suo stack è insolitamente grande .


2

Penserei che la ricorsione della chiamata di coda non sia utilizzata in Java principalmente perché ciò altererebbe le tracce dello stack e renderebbe molto più difficile il debug di un programma. Penso che uno degli obiettivi primari di Java sia consentire ai programmatori di eseguire facilmente il debug del loro codice e lo stack tracing è essenziale per farlo soprattutto in un ambiente di programmazione fortemente orientato agli oggetti. Dato che si potrebbe usare invece l'iterazione, il comitato linguistico deve aver capito che non vale la pena aggiungere la ricorsione della coda.

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.