Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

mmap sur un très gros volume ?

5 réponses
Avatar
pehache
Bonjour,

J'ai récupéré un code de calcul universitaire, qui travaille
entièrement en mémoire, notamment avec 3 très gros tableaux. J'ai
besoin de le tester avec des paramètres qui vont de loin excéder la RAM
disponible sur les machines dont je dispose.

Si j'avais eu Í  écrire un tel code, j'aurais géré ça avec des
fichiers pour stocker temporairement certains résultats et décharger la
RAM. En effet, l'algo travaille relativement séquentiellement sur chaque
tableau, et on pourrait ne garder en RAM que les parties utiles... Mais
modifier en profondeur le code existant prendrait trop de temps (pas que
ce serait compliqué, mais beaucoup d'endroits sur lesquels intervenir,
dans un code pas commenté, avec des gros risques d'erreur car des indices
dans tous les sens, etc...).

Je me demandais donc si utiliser des (très gros) fichiers memory mapped,
sur lesquels on ferait pointer les (très gros) tableaux, serait une
solution. Ca permettrait en tous cas de ne pas toucher Í  toute la partie
algo.. Je ne connais pas vraiment, et en plus le C moins j'en fais mieux
je me porte habituellement...

J'ai fait quelques tests avec des petits exemples trouvés sur le web, et
je vois que les flag utilisés dans la commande mmap() ont vraiment une
grosse influence sur ce qu'on peut faire et les performances.

MAP_NORESERVE : je n'ai pas compris exactement ce qu'il faisait, mais sans
lui on ne peut apparemment pas mapper un volume plus grand que la taille
RAM+swap. J'en ai donc besoin Í  priori.

MAP_SHARED : ça m'a l'air d'être exactement comme si on écrivait/lisait
directement dans le fichier, avec le cache RAM usuel. Les perfs sont
moyennes.

MAP_PRIVATE : perfs meilleures, mais les écritures donnent visiblement
lieu Í  de l'allocation de RAM, qui n'est ensuite pas libérée. Donc une
fois qu'on a écrit le volume correspondant Í  la taille de la RAM dans le
tableau, ça swappe (et quand on atteint le volume RAM+swap ça coince).
Une solution semble être de démapper/remapper de temps en temps pour
forcer la RAM allouée Í  se vider dans le fichier et Í  se libérer.

L'option MAP_NORESERVE + MAP_PRIVATE me semble préférable pour mon cas,
mais il y a peut-être mieux Í  faire, et je n'ai peut-être pas tout
compris...

Des conseils ?

5 réponses

Avatar
Alain Ketterlin
pehache writes:
[...]
Je me demandais donc si utiliser des (très gros) fichiers memory
mapped, sur lesquels on ferait pointer les (très gros) tableaux,
serait une solution. Ca permettrait en tous cas de ne pas toucher Í 
toute la partie algo.. Je ne connais pas vraiment, et en plus le C
moins j'en fais mieux je me porte habituellement...
J'ai fait quelques tests avec des petits exemples trouvés sur le web,
et je vois que les flag utilisés dans la commande mmap() ont vraiment
une grosse influence sur ce qu'on peut faire et les performances.
MAP_NORESERVE : je n'ai pas compris exactement ce qu'il faisait, mais
sans lui on ne peut apparemment pas mapper un volume plus grand que la
taille RAM+swap. J'en ai donc besoin Í  priori.

C'est juste le contraire : MAP_NORESERVE est une façon de dire que tu es
sͻr qu'il y aura de la place (RAM ou swap), et que ce n'est pas la peine
de s'en assurer au moment du mmap. Si j'ai bien compris ce que tu
expliques plus haut, tu es sͻr qi'il n'y aura pas de place. Donc, ton
programme va crasher (avec SIGSEGV) au premier accès au-delÍ  de la
quantité RAM+swap disponible.
MAP_SHARED : ça m'a l'air d'être exactement comme si on
écrivait/lisait directement dans le fichier, avec le cache RAM usuel.
Les perfs sont moyennes.

C'est le cas. Si ton mapping est basé sur un fichier existant, c'est ce
fichier qui sera utilisé pour le swap-out, et pas la zone de swap
(partition ou fichier) habituel.
MAP_PRIVATE : perfs meilleures, mais les écritures donnent visiblement
lieu Í  de l'allocation de RAM, qui n'est ensuite pas libérée. Donc une
fois qu'on a écrit le volume correspondant Í  la taille de la RAM dans
le tableau, ça swappe (et quand on atteint le volume RAM+swap ça
coince). Une solution semble être de démapper/remapper de temps en
temps pour forcer la RAM allouée Í  se vider dans le fichier et Í  se
libérer.

Attention, un map private n'est pas synchronisé avec le disque (en
général il n'y a pas de fichier associé, et s'il y en a un il est juste
utilisé pour l'initialisation -- c'est pour cela que "les perfs sont
meilleures"). Donc le swap-out se fait dans la zone de swap habituelle,
donc tu es limité en taille. Ce genre de mapping est utilisé pour la
zone de données des bibliothèques partagées par exemple : en début
d'exécution on crée un map private de la section de données du ELF, et
chaque écriture provoque une copie privée pour le processus.
L'option MAP_NORESERVE + MAP_PRIVATE me semble préférable pour mon
cas, mais il y a peut-être mieux Í  faire, et je n'ai peut-être pas
tout compris...

Avec NORESERVE+PRIVATE il te faut un espace suffisant dans RAM+swap, ce
qui ne semble pas être ton cas.
Ce que tu peux essayer est un mmap MAP_SHARED d'un fichier (existant)
qui a la taille de la mémoire dont tu as besoin. Tu fais un mmap de la
totalité, et tu laisses le système gérer le swap-in/-out (dans le
fichier donc). Puisque tu fournis l'espace de swap (le fichier), tu
n'auras pas de problème autre que les perfs (pendant le swap, qui sera
plus au moins fréquent selon le schéma des accès).
Le seul problème que tu pourrais avoir est de ne pas disposer dans
l'espace d'adressage de ton processus d'une plage assez grande. L'espace
d'adressage est en théorie de 2^64 octets, en pratique de 2^48 octets 256 To, mais il est déjÍ  un peu occupé par le texte, la pile, le tas,
les bibliothèques etc.
Le tout dépend de la taille des données. Si tu envisages vraiment plus
que ce qui peut tenir dans ton espace d'adresssage, il faudra gérer Í  la
main le swap-in/-out (ça s'appelle "out-of-core computation" en général,
et les techniques dépendent vraiment des algorithmes).
-- Alain.
Avatar
pehache
Le 07/05/2021 Í  17:07, Alain Ketterlin a écrit :
MAP_NORESERVE : je n'ai pas compris exactement ce qu'il faisait, mais
sans lui on ne peut apparemment pas mapper un volume plus grand que la
taille RAM+swap. J'en ai donc besoin Í  priori.

C'est juste le contraire : MAP_NORESERVE est une façon de dire que tu es
sͻr qu'il y aura de la place (RAM ou swap), et que ce n'est pas la peine
de s'en assurer au moment du mmap.

OK. En effet sans lui le mmap() retourne une erreur, et avec lui pas
d'erreur.
Si j'ai bien compris ce que tu
expliques plus haut, tu es sͻr qi'il n'y aura pas de place. Donc, ton
programme va crasher (avec SIGSEGV) au premier accès au-delÍ  de la
quantité RAM+swap disponible.

Ah lÍ  par contre non, ça ne plante pas en segv quand ça déborde. Ca
devient lent (si MAP_SHARED) ou bien le process est killé autoritairement
par l'OS (Debian 9) (si MAP_PRIVATE).
MAP_SHARED : ça m'a l'air d'être exactement comme si on
écrivait/lisait directement dans le fichier, avec le cache RAM usuel.
Les perfs sont moyennes.

C'est le cas. Si ton mapping est basé sur un fichier existant,

Oui je crée un fichier bidon avec la taille voulue.
c'est ce
fichier qui sera utilisé pour le swap-out, et pas la zone de swap
(partition ou fichier) habituel.

OK
MAP_PRIVATE : perfs meilleures, mais les écritures donnent visiblement
lieu Í  de l'allocation de RAM, qui n'est ensuite pas libérée. Donc une
fois qu'on a écrit le volume correspondant Í  la taille de la RAM dans
le tableau, ça swappe (et quand on atteint le volume RAM+swap ça
coince). Une solution semble être de démapper/remapper de temps en
temps pour forcer la RAM allouée Í  se vider dans le fichier et Í  se
libérer.

Attention, un map private n'est pas synchronisé avec le disque (en
général il n'y a pas de fichier associé, et s'il y en a un il est juste
utilisé pour l'initialisation -- c'est pour cela que "les perfs sont
meilleures"). Donc le swap-out se fait dans la zone de swap habituelle,
donc tu es limité en taille.

Oui, une fois RAM et swap remplis, c'est fini...
Mais je croyais qu'il y avait une synchronisation quand même, sauf
qu'elle n'était pas garantie jusqu'au moment du munmap(). En fait il n'y
en a pas du tout si je comprends bien.
L'option MAP_NORESERVE + MAP_PRIVATE me semble préférable pour mon
cas, mais il y a peut-être mieux Í  faire, et je n'ai peut-être pas
tout compris...

Avec NORESERVE+PRIVATE il te faut un espace suffisant dans RAM+swap, ce
qui ne semble pas être ton cas.

En effet. Je croyais pouvoir gérer ça par un unmap suivi d'un remap,
mais en fait non, les données sont perdues dans l'opération...
Ce que tu peux essayer est un mmap MAP_SHARED d'un fichier (existant)
qui a la taille de la mémoire dont tu as besoin. Tu fais un mmap de la
totalité, et tu laisses le système gérer le swap-in/-out (dans le
fichier donc). Puisque tu fournis l'espace de swap (le fichier), tu
n'auras pas de problème autre que les perfs (pendant le swap, qui sera
plus au moins fréquent selon le schéma des accès).

Apparemment ça va être la seule solution de toutes façons... Mais si
chaque affectation dans le tableau déclenche un write() sur le fichier,
ça risque de faire beaucoup d'overhead.
Le seul problème que tu pourrais avoir est de ne pas disposer dans
l'espace d'adressage de ton processus d'une plage assez grande. L'espace
d'adressage est en théorie de 2^64 octets, en pratique de 2^48 octets > 256 To, mais il est déjÍ  un peu occupé par le texte, la pile, le tas,
les bibliothèques etc.
Le tout dépend de la taille des données. Si tu envisages vraiment plus
que ce qui peut tenir dans ton espace d'adresssage, il faudra gérer Í  la
main le swap-in/-out (ça s'appelle "out-of-core computation" en général,
et les techniques dépendent vraiment des algorithmes).

Non lÍ  ça va aller, quand même :)
Avatar
Alain Ketterlin
pehache writes:
Ce que tu peux essayer est un mmap MAP_SHARED d'un fichier (existant)
qui a la taille de la mémoire dont tu as besoin. Tu fais un mmap de la
totalité, et tu laisses le système gérer le swap-in/-out (dans le
fichier donc). Puisque tu fournis l'espace de swap (le fichier), tu
n'auras pas de problème autre que les perfs (pendant le swap, qui sera
plus au moins fréquent selon le schéma des accès).

Apparemment ça va être la seule solution de toutes façons... Mais si
chaque affectation dans le tableau déclenche un write() sur le
fichier, ça risque de faire beaucoup d'overhead.

Non, ce n'est certainement pas Í  chaque accès, parce que la mémoire sera
gérée par pages de 4ko. Par contre, il vaut mieux être attentif Í  la
localité des données (comme pour tous les caches). Et il y a aussi
madvise()/posix_memadvise()... mais je ne sais pas vraiment ce qu'on
peut en attendre.
-- Alain.
Avatar
pehache
Le 07/05/2021 Í  18:00, Alain Ketterlin a écrit :
pehache writes:
Ce que tu peux essayer est un mmap MAP_SHARED d'un fichier (existant)
qui a la taille de la mémoire dont tu as besoin. Tu fais un mmap de la
totalité, et tu laisses le système gérer le swap-in/-out (dans le
fichier donc). Puisque tu fournis l'espace de swap (le fichier), tu
n'auras pas de problème autre que les perfs (pendant le swap, qui sera
plus au moins fréquent selon le schéma des accès).

Apparemment ça va être la seule solution de toutes façons... Mais si
chaque affectation dans le tableau déclenche un write() sur le
fichier, ça risque de faire beaucoup d'overhead.

Non, ce n'est certainement pas Í  chaque accès, parce que la mémoire sera
gérée par pages de 4ko.

Je voulais un appel Í  la fonction write(), même si l'écriture effective
sur le disque peut avoir lieu plus tard.
Par contre, il vaut mieux être attentif Í  la
localité des données (comme pour tous les caches).

Oui...
Merci pour les différentes explications, c'est un peu plus clair maintenant.
Avatar
pehache
Le 08/05/2021 Í  12:25, pehache a écrit :
Le 07/05/2021 Í  18:00, Alain Ketterlin a écrit :
pehache writes:
Ce que tu peux essayer est un mmap MAP_SHARED d'un fichier (existant)
qui a la taille de la mémoire dont tu as besoin. Tu fais un mmap de la
totalité, et tu laisses le système gérer le swap-in/-out (dans le
fichier donc). Puisque tu fournis l'espace de swap (le fichier), tu
n'auras pas de problème autre que les perfs (pendant le swap, qui sera
plus au moins fréquent selon le schéma des accès).

Apparemment ça va être la seule solution de toutes façons... Mais si
chaque affectation dans le tableau déclenche un write() sur le
fichier, ça risque de faire beaucoup d'overhead.

Non, ce n'est certainement pas Í  chaque accès, parce que la mémoire sera
gérée par pages de 4ko.

Je voulais ...

...dire...
...un appel Í  la fonction write(), même si l'écriture effective
sur le disque peut avoir lieu plus tard.
Par contre, il vaut mieux être attentif Í  la
localité des données (comme pour tous les caches).

Oui...
Merci pour les différentes explications, c'est un peu plus clair
maintenant.