I SyntaxNodes Roslyn vengono riutilizzati?


124

Ho dato un'occhiata a Roslyn CTP e, sebbene risolva un problema simile all'API dell'albero delle espressioni , entrambi sono immutabili ma Roslyn lo fa in un modo completamente diverso:

  • Expressioni nodi non hanno alcun riferimento al nodo genitore, vengono modificati utilizzando a ExpressionVisitored è per questo che le parti grandi possono essere riutilizzate.

  • Roslyn's SyntaxNode, dall'altra parte, ha un riferimento al suo genitore, quindi tutti i nodi diventano effettivamente un blocco impossibile da riutilizzare. Metodi come Update, ReplaceNode, ecc, sono forniti di apportare modifiche.

Dove finisce tutto questo? Document? Project? ISolution? L'API promuove una modifica graduale dell'albero (invece di un pulsante su), ma ogni passaggio ne fa una copia completa?

Perché hanno fatto una scelta del genere? C'è qualche trucco interessante che mi manca?

Risposte:


181

AGGIORNAMENTO: Questa domanda è stata oggetto del mio blog l'8 giugno 2012 . Grazie per l'ottima domanda!


Ottima domanda. Abbiamo discusso le questioni che sollevi per molto, molto tempo.

Vorremmo avere una struttura dati che abbia le seguenti caratteristiche:

  • Immutabile.
  • La forma di un albero.
  • Accesso economico ai nodi padre dai nodi figlio.
  • È possibile mappare da un nodo nell'albero a un offset di carattere nel testo.
  • Persistente .

Per persistenza intendo la capacità di riutilizzare la maggior parte dei nodi esistenti nell'albero quando viene apportata una modifica al buffer di testo. Poiché i nodi sono immutabili, non ci sono ostacoli al loro riutilizzo. Ne abbiamo bisogno per le prestazioni; non possiamo rieseguire l'analisi di enormi porzioni del file ogni volta che si preme un tasto. Dobbiamo ri-lex e rieseguire il parsing solo delle parti dell'albero che sono state influenzate dalla modifica.

Ora, quando provi a mettere tutte e cinque queste cose in una struttura dati, incappi immediatamente in problemi:

  • Come si costruisce un nodo in primo luogo? Il genitore e il bambino si riferiscono entrambi e sono immutabili, quindi quale viene costruito per primo?
  • Supponendo che tu riesca a risolvere quel problema: come renderlo persistente? Non è possibile riutilizzare un nodo figlio in un genitore diverso perché ciò implicherebbe dire al figlio che ha un nuovo genitore. Ma il bambino è immutabile.
  • Supponiamo che tu riesca a risolvere quel problema: quando inserisci un nuovo carattere nel buffer di modifica, la posizione assoluta di ogni nodo che è mappato in una posizione dopo quel punto cambia. Ciò rende molto difficile creare una struttura dati persistente, perché qualsiasi modifica può cambiare gli intervalli della maggior parte dei nodi!

Ma nel team di Roslyn facciamo regolarmente cose impossibili. In realtà facciamo l'impossibile mantenendo due alberi di analisi. L'albero "verde" è immutabile, persistente, non ha riferimenti padre, è costruito "dal basso verso l'alto" e ogni nodo tiene traccia della sua larghezza ma non della sua posizione assoluta . Quando si verifica una modifica, ricostruiamo solo le parti dell'albero verde che sono state influenzate dalla modifica, che in genere è circa O (log n) del totale dei nodi di analisi nell'albero.

L'albero "rosso" è una facciata immutabile che è costruita intorno all'albero verde; è costruito "dall'alto verso il basso" su richiesta e gettato via ad ogni modifica. Calcola i riferimenti padre producendoli su richiesta mentre scendi attraverso l'albero dall'alto . Produce posizioni assolute calcolandole dalle larghezze, di nuovo, mentre scendi.

Tu, l'utente, vedi sempre e solo l'albero rosso; l'albero verde è un dettaglio di implementazione. Se guardi nello stato interno di un nodo di analisi, vedrai infatti che c'è un riferimento a un altro nodo di analisi di un tipo diverso; questo è il nodo dell'albero verde.

Per inciso, questi sono chiamati "alberi rosso / verde" perché quelli erano i colori dell'indicatore della lavagna che abbiamo usato per disegnare la struttura dei dati nella riunione di progettazione. Non c'è altro significato per i colori.

Il vantaggio di questa strategia è che otteniamo tutte quelle grandi cose: immutabilità, persistenza, riferimenti ai genitori e così via. Il costo è che questo sistema è complesso e può consumare molta memoria se le facciate "rosse" diventano grandi. Attualmente stiamo effettuando esperimenti per vedere se possiamo ridurre alcuni dei costi senza perdere i benefici.


3
E per rispondere alla parte della tua domanda su IProjects e IDocuments: utilizziamo un modello simile nel livello dei servizi. Internamente ci sono tipi "DocumentState" e "ProjectState" che sono moralmente equivalenti ai nodi verdi dell'albero della sintassi. Gli oggetti IProject / IDocument che ottieni sono le facciate del nodo rosso per questi. Se osservi l'implementazione di Roslyn.Services.Project in un decompilatore, vedrai che quasi tutte le chiamate vengono inoltrate agli oggetti di stato interni.
Jason Malinowski

@ Eric scusa per l'osservazione, ma ti stai contraddicendo. The expense and difficulty of building a complex persistent data structure doesn't pay for itself.ref: stackoverflow.com/questions/6742923/… Se avevi obiettivi ad alte prestazioni, perché l'hai reso immutabile in primo luogo? C'è solo un'altra ragione oltre a quelle ovvie? ad esempio, più facile da rendere sicuro, ragionare su ecc.
Lukasz Madon

2
@lukas Stai prendendo quella citazione fuori contesto. La frase precedente era "Perché quando si guardano le operazioni che sono tipicamente eseguite su stringhe nei programmi .NET, non è affatto peggio creare semplicemente una stringa completamente nuova". OTOH, quando si guardano le operazioni che sono tipicamente eseguite su un albero delle espressioni, ad esempio digitando alcuni caratteri nel file sorgente, è significativamente peggiore costruire un albero delle espressioni completamente nuovo. Quindi ne costruiscono solo la metà.
Timbo

1
@lukas La mia ipotesi: dato che Roslyn dovrebbe operare su thread in background, l'immutabilità consente a più thread di analizzare lo stesso codice sorgente contemporaneamente senza preoccuparsi che venga modificato quando l'utente preme un tasto. In risposta all'input dell'utente, gli alberi immutabili possono essere aggiornati senza interrompere le attività di analisi in esecuzione. Quindi immagino che l'obiettivo principale dell'immutabilità sia rendere Roslyn più facile da scrivere (e forse più facile da usare per i clienti).
Qwertie

3
@lukas Le strutture dati persistenti sono più efficienti della copia, quando la struttura dati è in genere molto più grande delle modifiche ad essa. Il tuo punto, se ne hai uno, è perso per me.
Qwertie
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.