Lezione 1 di 7 · 15 min di lettura
Perché Mojo
Cos'è Mojo, cosa lo rende veloce, perché la velocità è anche una questione di energia. Poi lo installiamo e prepariamo il laboratorio per tutto il corso.
Il problema dei due linguaggi
Chi lavora con l’intelligenza artificiale, con la simulazione scientifica o con grandi quantità di dati vive una contraddizione quotidiana. Il codice si scrive in Python, perché è leggibile, veloce da modificare e ha un ecosistema sterminato. Ma il codice che fa il lavoro pesante (moltiplicare matrici, applicare una convoluzione, integrare un’equazione) non è Python: è C++, CUDA, Fortran o assembly, nascosto dentro librerie come NumPy e PyTorch.
Finché la libreria fa quello che ti serve, va tutto bene. Il giorno in cui ti serve un’operazione che non esiste, hai due strade: scriverla in Python, e pagare un rallentamento di decine o centinaia di volte, oppure scendere in C++ e CUDA, cambiando linguaggio, strumenti, compilatore e modo di ragionare. La documentazione di Mojo lo chiama il problema degli N linguaggi.
Mojo nasce per chiudere questa frattura: un solo linguaggio che si legge come Python ma che, sotto, ha tutto quello che serve per scrivere il codice del “piano di sotto”, fino ai kernel per GPU.
Cos’è Mojo, in concreto
Mojo è sviluppato da Modular, l’azienda fondata nel 2022 da Chris Lattner e Tim Davis. Il lavoro di Lattner lo usi già senza saperlo: ha creato LLVM (l’infrastruttura di compilazione dietro Clang, Rust e Swift) e ha guidato la nascita di Swift e di MLIR. Mojo è costruito proprio sopra MLIR. È open source (licenza Apache 2.0 con eccezioni LLVM) e dalla versione 1.0 segue il versionamento semantico.
Le sue caratteristiche, in ordine di importanza per questo corso:
- È compilato. Il tuo codice diventa istruzioni macchina native per il processore che hai davanti, prima di essere eseguito. Non c’è un interprete che legge il programma riga per riga.
- È tipizzato staticamente. Ogni variabile ha un tipo deciso a tempo di compilazione (
Int,Float64,String…). Il compilatore lo sfrutta due volte: per trovare errori prima dell’esecuzione, e per generare codice che lavora direttamente sui registri del processore. - Ha la sintassi della famiglia Python.
def, indentazione,for,if/elif/else, liste e dizionari: se hai scritto Python, lo leggi al primo colpo. Ma non è Python: niente variabili globali, niente tipi che cambiano a runtime, niente oggetti che si modificano al volo. - Parla la lingua dell’hardware moderno. I tipi numerici sono costruiti sopra i vettori SIMD (un’istruzione che lavora su 4, 8, 16 numeri contemporaneamente), e lo stesso linguaggio può essere compilato per CPU e GPU di produttori diversi.
- Si parla con Python. Un programma Mojo può importare moduli Python (NumPy compreso) e un programma Python può chiamare codice Mojo. Si può migrare un pezzo alla volta.
Dettagli nerd Interprete o compilatore: che differenza fa?
Un interprete (come CPython, quello che esegui con python3) legge il programma e lo esegue un’istruzione alla volta. Ogni volta che incontra a + b deve chiedersi: di che tipo è a? E b? Esiste un metodo __add__? Il risultato va allocato in memoria come nuovo oggetto? Sono decine di passaggi per una singola somma, ripetuti a ogni giro di ciclo.
Un compilatore ahead-of-time (come quello di C, Rust o Mojo) fa tutte queste domande una volta sola, prima dell’esecuzione. Se sa che a e b sono interi a 64 bit, a + b diventa una singola istruzione del processore, add, che richiede meno di un nanosecondo. È da qui che nascono le differenze di velocità di cinquanta o cento volte tra i due mondi.
Dettagli nerd Cosa sono LLVM e MLIR?
Un compilatore moderno lavora a strati. Il front-end capisce il tuo linguaggio e lo traduce in una rappresentazione intermedia (IR): un linguaggio interno, più semplice, pensato per essere ottimizzato. Il back-end traduce l’IR ottimizzata nel codice macchina di un processore specifico.
LLVM è la più diffusa di queste infrastrutture: un’IR unica più decine di back-end (x86, ARM, RISC-V, GPU NVIDIA e AMD…). Chi crea un linguaggio nuovo scrive solo il front-end e riceve in regalo ottimizzazioni e processori. MLIR è la sua evoluzione: invece di un’unica IR ne permette tante, a livelli diversi (“dialetti”), adatte anche a concetti come tensori e acceleratori. Mojo è costruito sopra MLIR, e per le CPU arriva fino a LLVM.
Quanto più veloce? Proviamo
Le promesse sulle prestazioni vanno misurate, non credute. Ecco lo stesso calcolo in Python e in Mojo: una stima di π con la serie di Leibniz, π/4 = 1 − 1/3 + 1/5 − 1/7 + …, sommando cento milioni di termini. Non preoccuparti della sintassi: la vedremo tutta.
def main():
terms = 100_000_000
total = 0.0
sign = 1.0
for k in range(terms):
total += sign / (2 * k + 1)
sign = -sign
print(4 * total)
main()def main():
var terms = 100_000_000
var total = 0.0
var sign = 1.0
for k in range(terms):
total += sign / Float64(2 * k + 1)
sign = -sign
print(4 * total)3.141592643589326Il codice Mojo è quasi identico: in più ci sono solo var, che dichiara una variabile, e Float64(...), una conversione esplicita da intero a decimale (in Mojo non avviene da sola: ne parleremo). Il risultato è lo stesso fino all’ultima cifra. Il tempo, no. Sul computer su cui è stato scritto questo corso (AMD Ryzen 7 5700G, Python 3.14, Mojo 1.1):
python3 leibniz.py 4.97 s
./leibniz (Mojo) 0.10 sCirca cinquanta volte più veloce, e senza alcun trucco: un solo core, nessun vettore SIMD, nessuna GPU, lo stesso algoritmo scritto nello stesso modo. È semplicemente la differenza tra interpretare e compilare. Nei moduli successivi vedremo come spingersi oltre, usando i vettori SIMD e tutti i core del processore.
Velocità è energia
C’è un motivo per cui la velocità conta anche fuori dalla classifica dei benchmark. Un processore sotto carico assorbe più o meno la stessa potenza qualunque cosa stia calcolando, e l’energia è potenza per tempo: se un calcolo finisce in un cinquantesimo del tempo, a parità di potenza consuma circa un cinquantesimo dell’energia.
Su un programma da un secondo non cambia niente. Su scala globale sì: secondo l’Agenzia Internazionale dell’Energia (rapporto Energy and AI, 2025) i data center hanno consumato circa 415 TWh nel 2024, intorno all’1,5% dell’elettricità mondiale, con una stima di più del doppio entro il 2030, trainata soprattutto dall’intelligenza artificiale. Addestrare e servire modelli significa miliardi di moltiplicazioni ripetute giorno e notte su migliaia di GPU: ogni punto percentuale di efficienza guadagnato nei kernel si traduce in megawattora e tonnellate di CO₂ risparmiate. Lo stesso vale per i modelli climatici, le simulazioni di fluidi e l’analisi dei dati satellitari, dove i calcoli durano settimane.
Uno studio accademico spesso citato (Pereira e colleghi, Energy Efficiency across Programming Languages, 2017) ha misurato gli stessi algoritmi in 27 linguaggi: Python ha consumato in media circa 75 volte l’energia del C. Mojo ha l’ambizione di darti l’efficienza del secondo con la comodità del primo.
Quiz
Perché lo stesso ciclo è molto più veloce in Mojo che in Python puro?
Come funziona questo percorso
Questo corso dà per scontato che tu abbia già programmato, in qualsiasi linguaggio: non spiega cos’è una variabile, ma spiega bene come Mojo le tratta e perché. Le lezioni vanno lette in ordine, e dentro ognuna trovi:
- Codice verificato: ogni programma e ogni output mostrato è stato compilato ed eseguito con Mojo 1.1 prima di finire qui. Il bottone “Copia” in alto a destra di ogni blocco lo copia negli appunti.
- Riquadri con consigli, avvertimenti, curiosità e confronti con altri linguaggi.
- Dettagli nerd: riquadri chiusi che spiegano cosa succede dentro la macchina (registri, SIMD, IEEE 754, UTF-8, linker…). Non servono per andare avanti, ma è lì che si capisce da dove arrivano le prestazioni.
- Quiz da fare qui nell’app, con la spiegazione subito dopo.
- Esercizi sul tuo computer: scrivi il programma, lo esegui e incolli qui l’output. L’app controlla se hai fatto centro.
Il Codex (tasto K, o il bottone in basso a destra) raccoglie parole chiave, tipi e comandi man mano che li incontri.
Cosa serve
Mojo gira su:
- Linux con glibc 2.34 o più recente (Ubuntu 22.04 o successive, Fedora, Arch, Debian 12…). Serve anche un compilatore C (
gccoclang), che Mojo usa come linker: su Ubuntu arriva consudo apt install build-essential. - macOS 15 (Sequoia) o più recente, solo su Apple silicon (M1 e successivi), con gli Xcode Command Line Tools.
- Windows tramite WSL, il sottosistema Linux di Windows, con una Ubuntu recente.
Il processore dev’essere un x86-64 dal 2013 in poi (livello x86-64-v3: Intel Haswell, AMD Zen e successivi) oppure un ARM64 recente. La GPU è facoltativa: per tutto questo corso basta la CPU.
Dettagli nerd Cos'è un linker, e perché Mojo ne vuole uno?
Il compilatore trasforma il tuo file in codice oggetto: istruzioni macchina con dei “buchi” dove il programma chiama funzioni che stanno altrove (per esempio la funzione che scrive sul terminale, che sta nella libreria di sistema). Il linker è il programma che cuce insieme il tuo codice oggetto e le librerie, riempie i buchi con gli indirizzi giusti e produce un eseguibile che il sistema operativo sa avviare. Mojo non ne include uno proprio: usa quello che trova nel sistema, raggiungibile attraverso il compilatore C.
Installare Mojo con uv
Mojo si installa come un pacchetto Python, con uv o con pixi. In questo corso usiamo uv, il gestore di pacchetti Python scritto in Rust: è velocissimo e gestisce da solo gli ambienti virtuali. Se non ce l’hai, installalo:
curl -LsSf https://astral.sh/uv/install.sh | shPoi crea la cartella che userai per tutto il corso, un progetto uv chiamato learn-mojo, e aggiungici Mojo:
uv init --bare learn-mojo
cd learn-mojo
uv add mojouv init --bare crea un progetto minimo (un solo file di configurazione, senza codice Python di esempio); uv add mojo scarica compilatore, libreria standard e strumenti dentro la cartella nascosta .venv del progetto. Sono qualche centinaio di megabyte, quindi la prima volta ci vuole un po’. Nella terza lezione guarderemo dentro tutti questi file.
Per usare il comando mojo devi attivare l’ambiente del progetto, una volta per ogni terminale che apri:
source .venv/bin/activateDa quel momento, in quel terminale, mojo è il compilatore del progetto. In alternativa puoi anteporre uv run a ogni comando (uv run mojo --version), che attiva l’ambiente solo per quel comando.
Verifica che tutto funzioni
Con l’ambiente attivo, chiedi la versione:
mojo --versionMojo 1.1.0 (8189361e)Il codice tra parentesi identifica la build esatta. Questo corso è verificato con Mojo 1.1.0: una versione più recente va bene, ma Mojo cambia in fretta, e se un esempio non compila più è quasi sempre per un’API rinominata. In quel caso il messaggio d’errore, di solito, suggerisce il nome nuovo.
Se il terminale risponde “comando non trovato”, quasi sempre hai dimenticato di attivare l’ambiente nel terminale corrente: rilancia source .venv/bin/activate dalla cartella learn-mojo.
Esercizio · sul tuo computer
Il laboratorio è pronto
- Installa
uv(se non ce l’hai) e crea il progettolearn-mojoconuv init --bareeuv add mojo. - Attiva l’ambiente con
source .venv/bin/activatee lanciamojo --version. - Apri la cartella
learn-mojonel tuo editor.
Quando mojo --version risponde con un numero di versione, hai finito: il compilatore è installato e ti aspetta.
Cosa succede adesso
Nella prossima lezione scriviamo il primo programma, lo eseguiamo, lo trasformiamo in un eseguibile vero e poi lo rompiamo in tutti i modi possibili, per conoscere il compilatore.