2012-03-24 7 views
7

Sono un programmatore che sviluppa un gioco online multiplayer utilizzando server basati su Linux. Usiamo un'architettura "istanziata" per il nostro mondo. Ciò significa che ogni giocatore che entra in un'area del mondo riceve una copia di quell'area per giocare con i suoi membri del gruppo, e indipendentemente da tutti gli altri giocatori che giocano nella stessa area.Utilizzo più efficiente della condivisione della memoria fork() e copy-on-write

Internamente utilizziamo un processo separato per ogni istanza. Inizialmente, ogni processo di istanza si avvierebbe, caricherà solo le risorse necessarie per l'area specificata, genererà il suo terreno casuale e quindi consentirà nuove connessioni dai giocatori. La quantità di memoria utilizzata da un'istanza era in genere di circa 25 meg, comprese le risorse e il livello generato in modo casuale con le entità.

Per ridurre il footprint di memoria delle istanze e accelerare il tempo di spawn, abbiamo modificato un approccio in cui creiamo una singola istanza master che carica tutte le risorse di cui ogni istanza potrebbe aver bisogno (circa 150 meg di memoria) e quindi quando è richiesta una nuova istanza, utilizzare la funzione fork() per generare una nuova istanza e utilizzare la condivisione della memoria copy-on-write in modo che la nuova istanza richieda solo la memoria per il proprio set di dati "univoco". L'impronta del livello generato a caso e delle entità che costituiscono i dati univoci per ogni istanza è di circa 3-4 megabyte di memoria.

Sfortunatamente la condivisione della memoria non funziona come credo. Un sacco di pagine di memoria sembrano diventare non condivise.

Dapprima, come si caricano più dei nostri dati impostati nel caso prefork, la memoria necessaria per ogni istanza biforcuta scende, ma alla fine v'è un punto di flesso in cui caricare più risorse prefork aumenta effettivamente i dati utilizzati dal ogni istanza biforcuta.

I migliori risultati che abbiamo avuto sono il caricamento di circa 80 meg del set di dati pre-fork, e quindi avere le richieste di istanze fresche caricare il resto. Ciò si traduce in circa 7-10 meg extra per istanza e un costo fisso di 80 megabyte. Certamente un buon miglioramento, ma non il migliore teorico.

Se carico l'intero set di dati da 150 meg e quindi fork, ogni istanza forked utilizza circa 50 megabyte di memoria! Significativamente peggiore del semplice non fare nulla.

La mia domanda è, come posso caricare tutto il mio set di dati nell'istanza di prefork, e assicurarmi di ottenere solo il set minimo di dati per istanza davvero unici come il footprint di memoria per ogni istanza.


Ho una teoria su ciò che sta accadendo qui e mi chiedevo se qualcuno sarebbe in grado di aiutare a confermare per me che questo è il caso.

Penso che abbia a che fare con la catena malloc gratuita. Ogni pagina di memoria dell'istanza di prefork probabilmente contiene alcuni punti liberi di memoria. Se, durante la generazione del livello casuale, viene allocato qualcosa che si adatta a uno degli spazi liberi in una pagina, allora l'intera pagina verrà copiata nel processo biforcato.

In Windows è possibile creare heap alternativi e modificare l'heap predefinito utilizzato dal processo. Se fosse possibile, rimuoverebbe il problema. C'è un modo per fare una cosa del genere in linux? Le mie indagini sembrano indicare che non puoi.

Un'altra possibile soluzione sarebbe se io potessi in qualche modo scartare la catena libera malloc esistente, costringendo malloc ad allocare memoria fresca dal sistema operativo per le chiamate successive. Ho cercato di esaminare l'implementazione di malloc per vedere se ciò sarebbe stato facilmente possibile, ma sembrava che potesse essere alquanto complesso. Se qualcuno ha qualche idea intorno a quest'area o un suggerimento su dove iniziare con questo approccio, mi piacerebbe sentirlo.

E infine se qualcuno ha altre idee su cosa potrebbe andare storto qui, mi piacerebbe davvero sentirli. Molte grazie!

risposta

2

In Windows è possibile creare heap alternativi e modificare l'heap predefinito utilizzato dal processo. Se ciò fosse possibile, rimuoverebbe il problema . C'è un modo per fare una cosa del genere in linux?

I Unix è possibile semplicemente mmap(2) memoria un bypass malloc del tutto.

Vorrei anche abbandonare l'intera faccenda "fare affidamento sulla mucca". Avrei il master process mmap un po 'di memoria (80M, 150M qualunque), scriverò roba in esso, segnalo in sola lettura tramite mprotect(2) per buona misura e prendilo da lì. Questo risolverebbe il vero problema e non ti costringerebbe a cambiare il codice lungo la strada.

+0

Non riesco a bypassare completamente malloc/new senza molto lavoro, perché si tratta di una grande base di codice esistente, e tutti i nostri caricatori di risorse li usano abbastanza volentieri. Non sono disposto a escludere l'approccio, ma mi piacerebbe continuare a utilizzare l'allocatore di memoria di qualcun altro, se possibile. – Negs

+1

@Negs È possibile utilizzare un allocatore personalizzato o addirittura dirottare 'malloc'. Tuttavia, leggi il secondo paragrafo. Smetti di contare sulla mucca e fai il tuo 'mmap'. – cnicutar

+0

Ciò mi richiederebbe ancora di scrivere un allocatore personalizzato e, mentre potrei ricorrere a questo, vorrei trovare una soluzione che non mi imponga di farlo. Qualche tipo di wrapper si avvicina al malloc esistente per facilitare forse i miei bisogni? – Negs

Problemi correlati