Aprire un programma su un PC degli anni ’90 emulato su hardware moderno può risultare sorprendente: c’è anche il museo virtuale con oltre 1700 sistemi operativi storici. Finestre che compaiono all’istante, menu immediati, interfacce che reagiscono senza esitazioni. L’impressione, per molti utenti, è che il software di un tempo fosse semplicemente più veloce. Vi ricordate la polemica intorno al fatto che Windows NT 3.51 (1995) apre più velocemente i programmi rispetto ai sistemi operativi di oggi?
Vogliamo dire la nostra. I computer dell’epoca erano lenti, spesso lentissimi rispetto agli standard attuali, ma gli sviluppatori lavoravano all’interno di limiti rigidi: pochi megabyte di RAM, processori con frequenze inferiori ai 100 MHz, dischi rigidi con prestazioni modeste e sistemi operativi che non potevano permettersi sprechi. Proprio quei vincoli hanno influenzato profondamente il modo di progettare il software.
La riflessione è tornata d’attualità dopo la pubblicazione dell’articolo “Old Software Was Fast Because It Had No Choice“: perché macchine migliaia di volte più potenti sembrano spesso offrire un’esperienza meno reattiva per attività quotidiane come aprire un file, avviare un’applicazione o navigare tra le impostazioni del sistema operativo?
Velocità percepita e velocità reale non sono la stessa cosa
Un PC del 1995 poteva avere un Pentium a 75-100 MHz, 8 o 16 MB di RAM e un disco IDE meccanico; oggi un notebook comune lavora con CPU multicore, SSD NVMe e 16 GB di memoria. Eppure alcune applicazioni moderne impiegano secondi per operazioni che, tecnicamente, richiederebbero millisecondi.
Chi ha utilizzato Lotus 1-2-3, Excel 2.0, AutoCAD o i primi software di elaborazione immagini sa bene che alcune operazioni richiedevano minuti. La ricostruzione di un foglio elettronico complesso poteva bloccare una macchina per parecchio tempo; la compilazione di un programma richiedeva pause oggi impensabili.
30 anni fa le attività computazionalmente intensive erano lente, ma l’interfaccia utente restava generalmente immediata. Aprire il menu Start, sfogliare una cartella, modificare un file di testo o lanciare un’applicazione leggera producevano una risposta quasi istantanea.
Il fatto è che la velocità percepita dall’utente dipende soprattutto dalla latenza delle interazioni più frequenti: un software può eseguire milioni di operazioni al secondo ma risultare comunque macchinoso se aggiunge ritardi inutili tra un’azione e la successiva.
L’importanza della latenza di input
La metrica più importante per l’utente non è il throughput, ma la latenza di input: il tempo tra un clic, una pressione di tasto o un comando e il primo aggiornamento visibile sullo schermo. Dan Luu ha misurato diversi sistemi con videocamera professionale mostrando che i dispositivi moderni possono avere latenze percepibili anche in scenari banali come la scrittura nella finestra del terminale.
La stessa pressione di un tasto attraversa più passaggi: firmware della tastiera, controller USB, polling HID, interrupt, scheduler del sistema operativo, coda eventi dell’applicazione, eventuale runtime gestito, compositore grafico, driver GPU, refresh del display. A 60 Hz un frame dura 16,6 ms; basta saltarne tre o quattro per trasformare una risposta teoricamente rapida in un ritardo visibile.
Le vecchie interfacce testuali avevano un vantaggio brutale: spesso scrivevano direttamente nella memoria video. In modalità testo su PC compatibile IBM, aggiornare pochi caratteri significava modificare celle in una schermata 80×25; niente layout dinamico, niente animazioni, niente DOM, niente compositing con trasparenze.
Non è un caso che Microsoft stia cercando di intervenire proprio su questo aspetto. Con Windows 11 l’azienda ha introdotto il nuovo Low Latency Profile, una tecnologia che aumenta il clock del processore (per al massimo 1-3 secondi) quando ci sono elaborazioni grafiche importanti, legate al funzionamento dell’interfaccia, da portare a termine.
Quando ogni kilobyte contava davvero
Un tempo le limitazioni hardware costringevano gli sviluppatori a una disciplina che oggi non sempre esiste.
Negli anni ’90 era normale misurare con attenzione l’occupazione della memoria, ridurre l’attività sul disco e ottimizzare il codice critico persino a livello assembler.
Molti prodotti commerciali costruirono la propria reputazione proprio sull’efficienza. I team al lavoro su WordPerfect, Turbo Pascal e su numerosi strumenti professionali dell’epoca investivano tempo e risorse per garantire tempi di avvio ridotti e un’interfaccia estremamente reattiva. La concorrenza era feroce e la velocità rappresentava una caratteristica facilmente percepibile dagli utenti.
Oggi la situazione è differente. Framework sempre più complessi, librerie esterne, componenti condivisi e sistemi di telemetria aggiungono livelli di elaborazione che spesso passano inosservati durante lo sviluppo ma diventano evidenti nell’uso quotidiano. Parlando di Windows, è ben nota la querelle sulle ripetute modifiche a livello d’interfaccia introdotte nel corso degli anni, spesso accettate controvoglia o addirittura ripudiate dagli sviluppatori.
La celebre osservazione nota come Legge di Wirth sostiene che il software rallenti più rapidamente di quanto l’hardware riesca a velocizzarsi. Formulata negli anni ’90, continua a essere citata perché descrive un fenomeno che molti utenti riconoscono ancora oggi.
Perché i vincoli producevano codice più disciplinato
Con 640 KB di memoria convenzionale in MS-DOS, ogni struttura dati contava.
Un programmatore non poteva caricare decine di librerie “per comodità”; doveva scegliere formati compatti, buffer riutilizzabili e algoritmi prevedibili: anche un dettaglio apparentemente piccolo, come evitare copie inutili di stringhe, faceva la differenza.
Il software moderno, invece, tende spesso ad accumulare dipendenze. Un’applicazione JavaScript può caricare migliaia di file tra pacchetti npm, polyfill, transpiler output, mappe sorgente, librerie UI e codice non realmente usato. Il problema non è JavaScript in sé; V8 ha un compilatore JIT avanzato e un garbage collector molto ottimizzato: il problema nasce quando si usa un motore potente per svolgere compiti semplici con troppi strati intermedi.
Electron, Chromium e il costo di portarsi dietro un browser
Abbiamo già detto, anche in altri nostri articoli, che molte applicazioni moderne attraversano una lunga catena di componenti: framework applicativi, runtime gestiti, librerie di terze parti, sistemi di rendering, motori JavaScript, servizi cloud e API remote.
Un esempio frequentemente citato è quello delle applicazioni desktop sviluppate con Electron: un programma apparentemente nativo incorpora un’intera istanza di Chromium e del motore JavaScript V8. Il vantaggio è evidente: lo stesso codice funziona su Windows, macOS e Linux. Il costo, però, si misura in memoria occupata, tempo di avvio e consumo energetico.
Electron eredita l’architettura multiprocesso di Chromium: un processo principale, processi renderer, IPC, sandboxing e integrazione con Node.js.
Un’app desktop tradizionale che mostra una lista, un menu e qualche pannello può usare direttamente Win32, Cocoa, GTK o Qt. Un’app Electron, invece, spesso avvia un runtime Chromium completo, un motore V8, un sistema di rendering HTML/CSS, code IPC e un ambiente Node. In pratica, per aprire una chat, un editor minimale o un client di note, il sistema può allocare centinaia di megabyte senza che vi sia la necessità di fare qualcosa di davvero complesso.
Chromium non è “lento” per natura: la sua architettura multiprocesso nasce anche per robustezza e sicurezza. Tuttavia, usare un browser completo come toolkit desktop per ogni applicazione sposta il costo di sicurezza, rendering e compatibilità anche dove non serve davvero.
Garbage collector, heap e pause non sempre invisibili
I runtime moderni gestiscono la memoria con tecniche molto sofisticate. V8, per esempio, usa il garbage collector Orinoco, con fasi concorrenti, parallele e incrementali. La JVM (Java Virtual Machine) offre collector come G1, ZGC e Shenandoah, progettati per ridurre le pause.
La gestione automatica della memoria, però, non elimina il costo delle allocazioni: se un’interfaccia genera migliaia di oggetti temporanei a ogni scorrimento della pagina (scroll), a ogni fase di rendering, a ogni aggiornamento dello stato dell’applicazione, prima o poi il garbage collector, il componente che libera la memoria non più utilizzata, dovrà esaminare l’intero heap, cioè l’area di memoria in cui vengono allocati gli oggetti. Anche pause brevi, ripetute nel momento sbagliato, producono micro-scatti.
In un’app a 60 fps il budget per frame è 16,6 ms; a 120 fps scende a 8,3 ms. Una singola pausa da 20 ms basta a far percepire una marcata “incertezza”, dal punto di vista della percezione dell’utente.
La corsa alle funzionalità e il valore della semplicità
Un tema emerso con forza riguarda l’accumulo continuo di funzionalità: strumenti un tempo essenziali stanno assumendo caratteristiche sempre più lontane dal loro scopo originario.
L’esempio del Blocco Note di Windows è emblematico: nato come editor minimale per file di testo, oggi integra schede multiple, funzioni avanzate e componenti legati all’intelligenza artificiale. Alcuni apprezzano queste aggiunte; altri ritengono che complicazioni simili aumentino il peso dell’applicazione senza migliorare realmente il lavoro quotidiano.
Lo stesso fenomeno interessa applicazioni mobili, piattaforme cloud e servizi web. In molti casi nuove funzionalità sostituiscono caratteristiche già esistenti e utilizzate dagli utenti. La conseguenza è che il software cresce in complessità senza necessariamente aumentare la produttività.
Telemetria, servizi remoti e dipendenze sempre attive
Molti rallentamenti percepiti oggi non dipendono dall’elaborazione locale: una quantità crescente di applicazioni effettua richieste verso servizi remoti già durante l’avvio.
Controlli degli aggiornamenti, sincronizzazione cloud, autenticazione, raccolta di dati diagnostici, caricamento di componenti dinamici e sistemi pubblicitari introducono inevitabilmente nuove attese.
Le vecchie applicazioni desktop lavoravano quasi esclusivamente in locale: un editor di testo apriva il file e basta; un file manager leggeva il contenuto del disco senza interrogare servizi esterni. La differenza non riguarda soltanto la velocità pura, ma anche la prevedibilità dell’esperienza.
In pratica, una parte della reattività perduta deriva dal fatto che molte operazioni dipendono ormai da elementi esterni al controllo dell’utente: connessioni Internet, server remoti, API di terze parti e infrastrutture cloud distribuite.
Cosa abbiamo guadagnato e cosa abbiamo perso
I sistemi operativi moderni offrono isolamento dei processi, protezioni avanzate della memoria, aggiornamenti automatici, supporto per hardware eterogeneo, funzionalità di accessibilità mature e livelli di sicurezza impensabili nell’era di Windows 95 e di Windows NT 3.51.
D’altra parte, sarebbe altrettanto sbagliato ignorare il problema della crescente complessità. Hardware enormemente più potente spesso compensa inefficienze che un tempo sarebbero state inaccettabili. L’abbondanza di risorse riduce la pressione a progettare software essenziale e snello.
Pensiamo ad esempio all’ampia disponibilità di “fiammanti” unità SSD NVMe: hanno ridotto drasticamente la latenza rispetto ai dischi meccanici e offrono una larghezza di banda assolutamente impensabile fino a qualche anno fa, ma non rendono gratis ogni accesso. Aprire migliaia di piccoli file, interrogare il registro di Windows, leggere configurazioni sparse e caricare DLL in cascata può introdurre ritardi misurabili. Il problema peggiora quando antivirus, indicizzatori, sincronizzazione cloud e controlli di reputazione analizzano gli stessi file.
Un’applicazione scritta con attenzione riduce il lavoro sui “percorsi critici”: in avvio, ad esempio, carica solo ciò che serve per mostrare la prima finestra, rimanda moduli secondari, evita scansioni complete della home directory e non inizializza servizi inutilizzati. Microsoft stessa, nelle linee guida per le app WinUI, invita a semplificare il primo frame e a caricare le funzioni non essenziali dopo che la finestra è interattiva.
Come si misura davvero il problema
Un team software serio non dovrebbe limitarsi a dire, riferendosi al programma sviluppato, “sul mio PC va veloce“: servono misure ripetibili.
Su Windows, Windows Performance Recorder e Windows Performance Analyzer (l’avevamo presentato anche per misurare il tempo di avvio del PC) permettono di analizzare CPU, disco, thread, DPC, interrupt e tempi di avvio. Process Monitor mostra accessi a file system, registro e processi in tempo reale.
Su Linux strumenti come perf, strace, eBPF e flamegraph aiutano a individuare syscall, lock e altre “funzioni calde”.
Il principio più efficace è semplice: non fare all’avvio ciò che puoi fare dopo. Il primo frame del software deve arrivare presto; la finestra deve diventare interattiva prima di caricare moduli secondari, plugin, modelli AI, suggerimenti, cronologia remota e tour guidati.
Dal punto di vista tecnico, significa ridurre la quantità di codice JavaScript caricata, rimuovere librerie e componenti non necessari, eliminare dal codice finale le parti inutilizzate tramite un efficace processo di tree shaking, caricare le risorse solo quando servono attraverso il code splitting, comprimere file e contenuti statici, evitare aggiornamenti dell’interfaccia superflui, gestire in modo efficiente elenchi molto lunghi visualizzando solo gli elementi necessari, limitare gli eventi monitorati a livello globale, ridurre le operazioni sincrone che bloccano l’esecuzione e spostare le elaborazioni più pesanti fuori dal thread dell’interfaccia utente, così da mantenere l’applicazione reattiva e fluida.
Nelle applicazioni native significa preferire strutture dati compatte, evitare allocazioni ripetute nel ciclo di rendering, mantenere cache piccole e misurate, usare I/O asincrono dove serve davvero, non bloccare il thread principale e fissare budget espliciti: tempo massimo di avvio, memoria massima a freddo, numero massimo di richieste prima dell’interattività.
Conclusioni
Il vecchio software non era migliore: aveva meno funzioni, meno sicurezza, meno compatibilità e spesso meno tolleranza agli errori. L’ambiente in cui era eseguito, tuttavia, obbligava chi lo scriveva a rispettare i limiti della macchina e, di conseguenza, il tempo dell’utente.
Non bisogna certo guardare con nostalgia a MS-DOS o riscrivere tutto in assembler: la chiave di volta consiste invece nel misurare la latenza, progettare il percorso critico, eliminare lavoro inutile, scegliere framework proporzionati al problema e trattare la reattività come una funzione del prodotto, non come una rifinitura finale.