Il numero ritirato

Published on

in

Restaurare OpenSimulator con Claude Code

3.216 eventi al secondo, «throughput triplicato». La cifra stava nella documentazione di CraftSim, un progetto di modernizzazione di OpenSimulator, e faceva la sua figura. Poi qualcuno è andato a controllare che cosa misurasse davvero lo script di prova: un percorso di codice che il server, in produzione, non attraversava mai. Lo strato asincrono che generava quel numero non era invocato da nessuna parte — codice morto. La cifra è stata formalmente ritirata dalla documentazione, e lo strato cancellato dal repository.

Bazzico OpenSim dal 2007 e di numeri tripli ne ho visti annunciare parecchi; una ritrattazione formale non la ricordo. Per questo il manuale tecnico di ZEngine/CraftSim, che documenta il tentativo di rendere OpenSim veloce e affidabile senza riscriverlo, mi interessa più per il metodo che per i risultati. Il manuale non dichiara mai con quali mani sia stato condotto il lavoro, ma si tradisce in una nota di passaggio: cita il CLAUDE.md del repository, il file di istruzioni permanenti che Claude Code rilegge all’inizio di ogni sessione.

Il problema di partenza lo conosce chiunque abbia amministrato una regione affollata. Migliaia di script LSL concorrenti, e i motori storici — XEngine prima, YEngine poi — che li interpretano istruzione per istruzione. I costi sono tre: la CPU non riesce a fare predizione dei salti su bytecode decodificato al volo; i tipi eterogenei delle liste finiscono impacchettati in oggetti sull’heap, e il garbage collector restituisce il favore con quel micro-singhiozzo che chiunque abbia guidato un veicolo scriptato riconosce al primo scatto; soprattutto, llSleep occupa un intero thread per tutta la durata della pausa — e con un pool spesso limitato a un centinaio di thread, bastano poche decine di script addormentati per paralizzare la regione.

ZEngine risponde compilando. Lo script viene tradotto in CIL, il linguaggio intermedio di .NET, ed emesso direttamente in memoria tramite ILGenerator — niente file C# intermedi, niente compilatore esterno; il JIT del runtime lo trasforma poi in codice macchina nativo. I tipi LSL diventano struct allocate sullo stack: un vettore è una Vector3, non una scatola polimorfa da inseguire nell’heap. Il pezzo elegante però è un altro: in ZEngine, llSleep non dorme. Solleva un’eccezione controllata, ZSleepException; lo scheduler la intercetta, salva il punto esatto di esecuzione, restituisce il thread al pool e affida il risveglio a un timer. Un thread solo può così presidiare centinaia di script in pausa. Il sonno smette di essere un’occupazione e diventa un segnalibro.

Fin qui l’ingegneria brillante, quella che si racconta volentieri. La parte istruttiva comincia quando i numeri non tornano. La build .NET 8 toccava un picco di 1.671 MB di RAM contro i 209 del server liscio, e la prima stesura del manuale presentava la cosa come un compromesso strutturale: più velocità, più memoria, prendere o lasciare. Non era vero. Era il Server GC, che alloca un heap e un thread di raccolta per ogni core logico della macchina — sedici, su quella di prova — a beneficio di un carico dominato da un unico ciclo di heartbeat e quattro thread dedicati agli script. Ripetuto il collaudo su .NET 9 con Workstation GC: 466 MB di picco, −72%, stesso numero di eventi al secondo. Il «costo intrinseco dell’architettura» era un’opzione di configurazione. C’è pure un’ironia di contorno: proprio .NET 9 ha reso il Server GC meno vorace attivando di serie DATAS, il meccanismo che adatta l’heap alla dimensione effettiva dell’applicazione — e infatti anche lì il picco scende, a 704 MB. Non abbastanza: per un carico come questo, riconosce la stessa squadra del GC di Microsoft, Workstation resta la scelta corretta.

E si arriva così ai commit più interessanti del progetto, che sono quasi tutti sottrazioni. Lo strato asincrono ereditato da YEngine — quello dei 3.216 eventi — eliminato in blocco, perché nessun percorso reale del server lo chiamava. Diverse conversioni asincrone riportate al sincrono dopo che avevano introdotto difetti veri: il caricamento dei prim all’avvio, che in versione asincrona poteva saltare in silenzio l’attivazione degli script; il salvataggio del terreno che, affidato a un «lancia e dimentica», rischiava di essere troncato dallo spegnimento del processo. Le letture multiple di inventario, prima parallelizzate e poi ri-serializzate quando le prove in produzione hanno rivelato condizioni di corsa sulla stessa connessione al database. La formula del manuale è asciutta: la correttezza ha avuto priorità sul guadagno teorico.

Poi c’è la fauna dei difetti quindicennali. Contatori di scena mutati con due meccanismi che si ignorano a vicenda — incremento sotto lock in un punto, scrittura atomica Interlocked in un altro — con conseguente deriva progressiva del conteggio degli avatar. Una cache di attachment per NPC che espelleva le voci in base alla data di creazione dell’oggetto sorgente anziché al momento dell’inserimento: superata la soglia, buttava via ciò che era ancora in uso perché l’originale era «vecchio». Roba rimasta lì per anni sotto gli occhi di una comunità intera. Come, francamente, boh. Ma so per esperienza che nessun volontario apre il sabato pomeriggio cinquanta progetti C# per verificare tutti i punti in cui viene toccato m_numRootAgents.

Un attrezzo agentico, invece, lo fa senza lamentarsi. È qui il contributo concreto di Claude Code a un lavoro del genere: legge cinquanta .csproj senza saltare il quarantunesimo, segue ogni scrittura di un contatore lungo il grafo delle chiamate, propone la correzione noiosa — Interlocked dappertutto — invece di quella eroica. Il rovescio è altrettanto concreto: lasciato a briglia sciolta, un assistente avvolge volentieri ogni metodo in un Task.Run e lo chiama progresso, cioè esattamente l’asincronia indiscriminata che questo progetto ha dovuto disfare. La differenza la fa il mandato scritto. Il CLAUDE.md citato di sfuggita nel manuale serve a questo: è il posto dove si fissano i vincoli — cosa non toccare, cosa dimostrare prima di unire, quando preferire il sincrono — e con questi strumenti i vincoli valgono più dell’entusiasmo.

Il metodo complessivo somiglia da vicino a quello che Cesare Brandi codificò per il restauro: intervento riconoscibile e reversibile, rimozione delle aggiunte spurie, mai ridipingere. Il tag dotnet8 lasciato nel repository come ultimo commit stabile prima della migrazione è la reversibilità; la cancellazione del codice morto è la rimozione dell’aggiunta spuria; il numero ritirato è la patina falsa che si stacca dalla tela e lascia vedere il colore vero — meno brillante, più solido.

Cosa non dicono, i dati: il collaudo è uno scenario solo, cento NPC e un migliaio di script su una macchina, e a quella scala anche il server liscio regge — 55 fotogrammi al secondo per tutti, nessun collasso. Il +49% di eventi script, da 333 a 496 al secondo, pesa sulle regioni fittamente scriptate — la mia, dove gli NPC di Nexus dialogano via HTTP con i modelli, è tra queste — e quasi niente sulla sim scolastica con dieci avatar. E sopra tutto pende la domanda che accompagna ogni miglioria di OpenSim da vent’anni: finirà nel ramo principale o resterà un fork? Il cimitero è affollato: Aurora diventata WhiteCore, InWorldz diventata Halcyon, tutte più rapide del capostipite sulla carta, tutte ferme da anni.

Di motori tre volte più veloci, quel cimitero è pieno. Di progetti che ritirano i propri numeri quando scoprono che misuravano il vuoto, ne conosco uno. La velocità si compra con un compilatore; l’affidabilità comincia il giorno in cui si butta la cifra che faceva comodo.


Riferimenti

[1] Progetto CraftSim, ZEngine: Manuale Tecnico e Analisi Architetturale del Codice Sorgente in OpenSimulator, 2026. Fonte primaria dell’articolo: collaudo bench_heavy.py, correzioni di concorrenza, migrazione .NET 9. Documento di progetto [PDF].

[2] OpenSimulator, Codebase overview, wiki ufficiale. Organizzazione del codice del simulatore. URL: http://opensimulator.org/wiki/Codebase_overview

[3] Microsoft, What’s new in .NET 9, 2024. DATAS attivo di serie al posto del Server GC classico; miglioramenti a JIT e raccolta. URL: https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview

[4] Microsoft, Dynamic adaptation to application sizes (DATAS). Come l’heap viene adattato alla dimensione effettiva dell’applicazione. URL: https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/datas

[5] Maoni Stephens, Preparing for the .NET 10 GC, .NET Blog, 2025. Quando Workstation GC resta la scelta corretta anche dopo DATAS. URL: https://devblogs.microsoft.com/dotnet/preparing-for-dotnet-10-gc/

[6] Anthropic, How Claude remembers your project, documentazione Claude Code. Il CLAUDE.md come istruzioni persistenti di progetto. URL: https://code.claude.com/docs/en/memory

Leave a Reply

Discover more from Salahzar's Weblog

Subscribe now to keep reading and get access to the full archive.

Continue reading