Stadia e lo scheduler di Linux: misteriosi problemi per gli sviluppatori

Google si sta impegnando molto a dare supporto a Google Stadia e portare nuovi giochi sulla piattaforma, soprattutto in questo periodo che non è ancora aperto a tutti gli utenti ma solo a chi ha fatto il preordine della Founder Edition.

Una notizia molto curiosa ci arriva però dallo sviluppatore Malte Skarupke, mentre cercava di effettuare il porting di Rage 2 su Stadia, incappando in misteriosi problemi inizialmente inspiegabili.

Che cos’è lo scheduler?

Per chi non sapesse di cosa stiamo parlando, lo scheduler è una componente molto importante, oserei dire cruciale, nei moderni sistemi operativi, e si occupa di gestire l’esecuzione dei vari processi in concorrenza in un certo ordine in modo da garantire la corretta esecuzione di tutti i processi nel minor tempo possibile.

Grazie a lui, per esempio, abbiamo la percezione che il nostro computer stia eseguendo decine se non centinaia di processi in contemporanea ma in realtà ne esegue solo un paio alla volta (il numero è strettamente legato al numero di core sulla nostra macchina) per un periodo limitato di tempo, per poi interromperlo e dare la precedenza a qualche altro processo.

Vari stati di un processo o thread in Linux

L’operazione di messa in pausa di un processo per lasciare spazio ad altri processi e chiamata context switching ed è gestita dal sistema operativo tramite speciali algoritmi di scheduling.

La gestione dei thread: spinlock e mutex

Non è questa la sede per dare spiegazioni su cosa siano gli spinlock e i mutex e come vengono gestiti correttamente i programmi multi-thread, alla base di programmi complessi che richiedono elevata capacità computazionale (soprattutto i videogiochi).

E’ giusto però dare un’infarinatura generale per spiegare meglio i problemi in cui è incappato lo sviluppatore.

Nella scrittura di un programma multi-thread, spesso si incorre in problemi di concorenza su una certa risorsa (e se non si sta attenti si può arrivare al dead lock!). Per gestire questi problemi di concorrenza, si adottano soluzioni per bloccare l’utilizzo di una risorsa da parte di un thread durante l’utilizzo da parte di un altro thread: si dice infatti che i due thread operano in mutua esclusione su quella stessa risorsa.

I moderni sistemi operativi, in questo caso quelli basati su linux, offrono delle librerie per aiutare nella gestione di queste problematiche. Si parla quindi di “mutex” e “spinlock”, due tecniche simili ma con delle diversità che permettono la gestione di accessi concorrenziali.

Potete vedere sia il mutex che lo spinlock come una variabile binaria, 0 o 1. Di default è a 1, se un thread vuole accedere ad una risorsa del sistema o del programma, prova a bloccare la variabile e qui possono succede due cose:

  1. Se la variabile è a 1: nessun processo sta usando la risorsa, mette a 0 la variabile e opera sulla risorsa. Alla fine, riporta la variabile a 1 per dare ad altri la possibilità di utilizzare la risorsa.
  2. Se la variabile è a 0: qualcun altro sta usando la risorsa, il processo non può quindi accedere alla risorsa e qui avviene qualcosa di particolare.

Nel caso in cui il thread si trovi davanti a una variabile a 0, se si sta utilizzando una variabile “mutex” allora il thread viene messo in “sleep” da parte del sistema operativo fino a quando il mutex non verrà rimesso a 1. Allora, il processo verrà risvegliato e proverà a riprendere il possesso della variabile (se non arriva qualcun altro prima!).

Se si utilizza invece uno spinlock, il thread non viene mai interrotto e continua a provare e riprovare fino a quando non riuscirà a prendere il controllo della risorsa.

Questo secondo modo di operare ovviamente porta ad un consumo di risorse inutili (il thread continua a consumare cicli di CPU inutilmente perchè controlla di continuo la disponibilità della risorsa). Nel caso dei mutex invece ci si trova davanti ad una migliore gestione delle risorse computazionali, ma c’è un maggiore effort perchè il thread va poi risvegliato e portato dallo stato “sleep” a “run”.

Il programmatore deve quindi di volta in volta scegliere quale dei due approcci adottare, in base al contesto in cui si trova e deve operare il programma.

Il caso dello sviluppatore Stadia

Lo sviluppatore, nel suo caso, è incappato in misteriosi stalli da parte di Rage 2 durante il porting. Ha infatti optato per l’utilizzo di spinlock, solo che il “via libera” veniva dato anche con più di 1ms di ritardo nel caso in cui fosse già libero!

Peccato che 1ms è veramente tanto, e questo ritardo è colpa dello scheduler di Linux che non è così tanto efficiente. Un gioco a 60 fps genera frames ogni 16ms circa, e 1ms è un ritardo perfettamente paragonabile a 16ms! E ciò è inaccettabile per avere una buona resa del videogioco. E per piattaforme quali Stadia, la velocità è tutto!

“L’unica cosa in comune a tali stalli è data dal fatto che usano gli spinlock. Situazione curiosa dato che sono stato proprio io a scrivere gli spinlock.
Il problema è che un thread impiega diversi millisecondi per acquisire uno spinlock anche quando nessun altro thread ne è in possesso.
In un videogioco dove bisogna mostrare un’immagine a schermo ogni 16 ms uno stallo che impiega più di 1 ms è terribile. Siamo riusciti a risolvere il problema rimpiazzando gli spinlock con i mutex.”

I problemi si risolverebbero adottando su Linux un altro scheduler. Attualmente si utilizza JobScheduler.

“Per anni abbiamo eseguito i nostri carichi di lavoro su Linux utilizzando JobServer indicando più core di quanti ce ne fossero realmente sulla macchina (quindi abbiamo fatto -j6 o più su un quad core per esempio) pensando che il Jobserver non usasse effettivamente il numero di core indicato. Ma adesso si è dimostrato che non erano affatto in errore, ma c’era una scarsa gestione dei core.”

Lo sviluppatore ha quindi provato con MuQSS ottenendo maggiori dei vantaggi, a discapito di altri problemi. Spera quindi che vengano risolti alcuni problemi presenti in MuQSS, con la speranza che venga utilizzato di default nelle future versioni del kernel Linux e che magari Google stessa inizi ad utilizzarlo, non solo su Stadia.

Potete trovare maggiori dettagli tecnici e benchmarks in questo articolo dello sviluppatore.