OVH Cloud OVH Cloud

Allouer plus de mémoire que disponible

76 réponses
Avatar
Rémi Moyen
Salut,

J'ai une question sur la mani=E8re dont se comporte un programme sous
Linux, en C++, quand on essaye d'allouer plus de m=E9moire que
disponible sur le syst=E8me. =C0 la base, l'op=E9rateur new lance dans ce
cas-l=E0 une exception et c'est =E0 moi de la rattraper et de la traiter,
tr=E8s bien.

Mais mon probl=E8me est de savoir si, par derri=E8re, le noyau essaye
d'allouer toute la m=E9moire avant de se rendre compte qu'il n'en a pas
assez de disponible, ou est-ce qu'il fait d'abord une v=E9rification
avant de se lancer l=E0-dedans ?

Dans le cas o=F9 la m=E9moire est sur de la RAM, =E7a ne change probablement
pas grand chose (en perception par l'utilisateur), mais si il y a du
swap, est-ce que le noyau va faire swapper tout ce qu'il peut pendant
une heure avant de se rendre compte qu'il lui manque 42 octets ?

Si c'est le cas, alors j'imagine que j'ai int=E9r=EAt =E0 impl=E9menter
quelques checks rapides sur la m=E9moire disponible avant de commencer =E0
allouer des blocs de 1 Go (=E7a m'arrive...), sinon mes utilisateurs
risquent de ne pas trop aimer devoir attendre longtemps, et swapper
tout les autres programmes, avant de s'entendre dire qu'il n'y a pas
assez de m=E9moire pour faire ce qu'ils veulent ! Et dans ce cas-l=E0
encore, y'a-t-il une mani=E8re plus ou moins "standard" de v=E9rifier "=E0
la main" la m=E9moire disponible ?

Et accessoirement, j'imagine que diff=E9rents OS peuvent se comporter
diff=E9remment de ce point de vue, donc est-ce pareil pour SunOS ?
IRIX ? (les deux autres syst=E8mes que je suis susceptible d'utiliser)

Merci d'avance !
--
R=E9mi Moyen

10 réponses

1 2 3 4 5
Avatar
Marc
Michel Talon wrote:

Tu oublies solaris qui ne fait pas d'overcommit (il n'y a aucune option
pour le permettre) et s'en sort bien quand même.


Oui ça leur permet de vendre des machines avec beaucoup de gigaoctets de
mémoire et de téraoctets de disque.


Euh non, solaris n'utilise pas plus de mémoire que linux, il en exige
plus, mais il suffit que ce soit un gros espace de swap, ce qui est
quasiment gratuit (surtout si on met comme swap un fichier compressé ou
quelque chose comme ça). Les grosses machines que vend Sun tournent plus
souvent sous linux que sous solaris de toute façon.

J'ai trouvé une discussion de la question ici:
http://developers.sun.com/solaris/articles/subprocess/subprocess.html


On a les mêmes sources :-)

sous Solaris il existe un appel système pour
permettre l'overcommit de certains buffers mmap(MAP_NORESERVE)


J'avais complètement oublié ça...


Avatar
Rémi Moyen
On Feb 16, 3:05 pm, Pascal Bourguignon wrote:

Il y a ulimit, qui permet de mettre une limite (fixe ou variable) sur
la consomation de ressources d'un processus (et fils).


Ah oui, je n'y pensais plus. Donc si je dépasse la taille fixée par
ulimit, mon appel à 'new' devrait immédiatement échouer, sans ess ayer
de marquer toutes les pages une par une, c'est ça ? Dans ce cas, ça
va, ça ne complique pas trop mon travail. Ouf.


Non.

Enfin, le problème c'est qu'il y a plusieurs couches!
Tu nous parles de new(), mais le système n'a rien à voir avec new().


Ben oui, je sais, mais moi concrétement, c'est new qui m'intéresse. Je
veux dire, ça m'intéresse de comprendre comment ça marche en dessous
mais in fine, je veux produire un code C++ correct, donc c'est à ce
niveau que j'ai vraiment besoin de savoir ce qu'il faut que je fasse.

[snip les explications et exemples qui éclairent bien les choses]

La question, c'est comment se comporte new()? Comment est elle
implémentée?


Je sais pas... c'est aussi la question que je me pose (et que je pose
aux gens ici...) !

Merci pour tes explications !
--
Rémi Moyen



Avatar
Marc
Michel Talon wrote:

http://developers.sun.com/solaris/articles/subprocess/subprocess.html
On y apprend que sous Linux, même quand l'overcommit est désactivé, il y
est quand même (!)


En même temps l'auteur ne semble pas connaître la valeur "3" du paramètre
permettant de régler l'overcommit, donc ce n'est pas forcément fiable
comme info.

Avatar
talon
Rémi Moyen wrote:

Oui enfin le problème du swap, c'est que je peux allouer la mémoire
par blocs de 1 Go ou 2. Dans ce cas, le temps de swapper assez pour me
faire de la place dans la RAM n'est pas négligeable et si je fais
cette opération pour rien (i.e. qu'à la fin de mon allocation de 1 Go
je me rends compte que je ne peux récupérer que 900 Mo et que mon truc
échoue), j'ai un peu gâché plein de temps.


Mais ce n'est pas parceque le noyau t'alloue 1 Go qu'il se dépêche de
swapper pour que tu puisses bénéficier de 1 Go en mémoire physique. En
fait il t'alloue 1 Go de mémoire *virtuelle* et c'est uniquement au
moment de s'en servir, page par page, qu'il va swapper des choses pour
faire de la place. Peut être qu'il n'aura même pas besoin de swapper du
tout parceque d'autres pages seront devenues naturellement libres, et
donc réutilisables directement sans avoir besoin de les copier en swap.
Donc j'ai l'impression que le problème que tu te poses est un faux
problème.


D'où ma question initiale. Je voudrais juste être sûr que quand le
noyau commence à swapper des trucs, il ne le fait pas pour rien. Donc


Mais ce n'est pas toi qui gère ça, ni qui doit le gérer, c'est le noyau
qui le fait en fonction de la connaissance qu'il a de l'ensemble des
requêtes au système de mémoire virtuelle. Depuis très longtemps sous
FreeBSD et plus récemment sous Linux, il maintient des statistiques sur
l'utilisation des pages, et swappe les moins utilisées. Donc tu devrais
pouvoir compter sur le noyau pour résoudre optimalement ton problème
d'allocation de mémoire sans que tu ais à y penser en quoi que ce soit.
Toute mesure que tu prendrais pour améliorer les choses risque de les
rendre pire, parceque dans ton raisonnement tu négliges complètement les
requêtes des autres processus.


Mais tant que je n'ai pas rempli la dernière page, je ne sais pas si
elle sera disponible quand j'écrirais dedans ? Et donc si elle n'est
pas disponible, j'aurais passé du temps pour rien à écrire toutes les
autres pages (et potentiellement à envoyer plein d'autres trucs sur le
swap pour me donner ces pages-là en RAM) ?


Oui. Tu seras mort pour rien. Voilà pourquoi il faut *beaucoup* de swap
même si on ne compte pas en utiliser beaucoup. L'argument, le swap est
lent, si on utilise le swap on va ramer, etc. ne vaut rien. Le swap est
une sécurité pour le cas où l'overcommit partirait en vrille. Ca ne veut
pas dire qu'on va l'utiliser beaucoup dans l'usage courant.
Si ton problème a des exigences de réponse rapide telles que tu ne peux
tolérer aucun ralentissement du à un swap éventuel, il te fait avoir
beaucoup de mémoire, et réserver la mémoire à l'avance. Comme on le
disait, ça veut dire écrire partout et verrouiller en mémoire avec
mlock(). Ca veut dire aussi que *tous* les autres processus seront
réduits à la portion congrue, en permanence, même quand ton processus
n'a pas besoin réél de cette mémoire. Ca veut dire en particulier que
ce n'est pas l'opérateur new de C++ qui va le faire. En pratique cette
technique est totalement équivalente à utiliser une machine à mémoire
paginée comme une machine de grand papa où le programme était résident,
et est donc complètement stupide. La bonne technique est d'avoir du swap
en surabondance pour éviter le problème à tout prix, et en second, de
dimensionner la ram pour que le système ne se mette à swapper que
rarement. Tu connais la taille maximale de ton programme, il te suffit
d'avoir un peu plus de ram (il faut penser aux autres programmes) et
le problème est résolu.

--
Rémi Moyen



--

Michel TALON

Avatar
Pascal Bourguignon
"Rémi Moyen" writes:
En même temps, quand dans mon code je fais un float* toto = new
float[1000000000], je m'attends à ce que, si new() n'échoue pas, j'ai
un tableau de 1 milliard de cases et où je puisse écrire n'importe où.

Tu vas me dire, la solution est, comme tu le dis, d'aller écrire sur
toutes les pages. Mais dans ce cas, je tombe dans le problème décrit
plus haut de faire swapper 900 Mo avant de me rendre compte que j'ai
pas assez de place. D'où ma question initiale, est-ce qu'il y a un
moyen de faire ça proprement et sans que ça prenne trop de temps ?


Non justement, car c'est une question qui dépend du temps!

Si il faut 2 secondes pour écrire sur chaque page, que lorsque tu
commence il n'y a de la place que pour la moitié, mais qu'au bout de
0.9 la commande: swapon /var/swap/new-1GB-swap est exécuté, tu peux
finalement terminer de toucher chaque page au bout des deux secondes.

De même, si quand tu commence, il y a assez de place, mais qu'à mi
chemin, l'admin fait: swapoff /var/swap/old-1GB-swap
tu peux trés bien ne pas pouvoir terminer.


Mais ça veut aussi dire que je prends le risque que mon 'new' marche
sans problèmes mais qu'après, à un moment quelconque et aléatoire, mon
appli crash (ou qu'un process se fasse tuer) sans que je puisse rien y
faire. Je suis censé hausser des épaules et dire "c'est normal", ou
y'a une stratégie propre pour éviter ça ???


La réponse, c'est que ce n'est pas quelque chose qui se gère au niveau
du programme, mais au niveau de la chaîne d'exploitation. On peut
utiliser un ordonanceur qui évitera de lancer simultanément des
programmes qui demanderaient au total plus de mémoire que disponible.
Ou on peut le faire à la main, en évitant de lancer trop de programmes
simultanément.


--
__Pascal Bourguignon__ http://www.informatimago.com/

Avatar
Pascal Bourguignon
"Rémi Moyen" writes:
D'où ma question initiale. Je voudrais juste être sûr que quand le
noyau commence à swapper des trucs, il ne le fait pas pour rien. Donc
que si il commence à swapper pour me donner mon Go, c'est que je
l'aurais au final. Je crois comprendre de tes explications qu'en fait,
ce que je demande est impossible, que le seul et unique moyen de
vérifier que mon bloc de mémoire est vraiment disponible, c'est
d'écrire dedans et donc potentiellement de swapper si il faut.

Ça ne m'arrange pas, mais si c'est comme ça, tant pis, les
utilisateurs devront s'y faire...


C'est à dire que si tu utilise sur linux la commande free (il y a peut
être une commande similaire sur ton unix),

% free
total used free shared buffers cached
Mem: 1033972 1014484 19488 0 50816 496288
-/+ buffers/cache: 467380 566592
Swap: 5976000 752 5975248

on voit ici qu'il y a 19488 Ko libres (1 processus pourrait donc
allouer et écrire dans 19488 Ko immédiatement), en plus, il y a 467380
Ko utilsé comme cache disque dur c'est à dire qu'ils sont récupérable
immédiatement, donc un processus pourrait sans difficulté allouer et
écrire immédiatement dans 566592 Ko au total.

Ensuite, on bascule dans le swap, et ona alors presque 6 Go disponible,
c'est à dire plus que ce qu'un processus ne pourrait allouer dans un
espace de mémoire virtuelle de 32 bits de toutes façons.


Mais la question, c'est qu'il y a plein de processus qui fonctionnent
en même temps: il faut éviter que le total de l'espace alloué et écrit
par tous les processus ne dépasse ces limitets.

Dans ce cas, j'ai l'impression que ça veut dire qu'il faut plus ou
moins que j'implémente un micro-garbage collector pour savoir combien
de mémoire j'utilise ?? Ça me semble bizarre, j'ai dû mal
comprendre...


Non, ça veut dire qu'il faut te dépêcher de remplir toutes les pages
que tu as demandées, une à une, si tu veux être sur de les avoir pour
toi, et tu risques de te faire tuer s'il n'y a plus de place.


Mais tant que je n'ai pas rempli la dernière page, je ne sais pas si
elle sera disponible quand j'écrirais dedans ? Et donc si elle n'est
pas disponible, j'aurais passé du temps pour rien à écrire toutes les
autres pages (et potentiellement à envoyer plein d'autres trucs sur le
swap pour me donner ces pages-là en RAM) ?


Oui, avec l'overcommit, et considérant qu'il y a d'autres processus
tournant sur le même système.

C'est plus efficace de prendre ce risque. Dans les anciens systèmes
mainframes, on devait déclarer pour chaque processus les tailles de
mémoire et fichiers nécessaires, qui étaient allouées au début du
processus par le système, que le programme l'utilise en entier ou pas.
Il y avait donc beaucoup de perte, de mémoire non utilisée et de temps
CPU non utilisé (car on ne pouvait pas ordonancer plusieurs programmes
simultanément s'ils annonçaient avoir besoin de plus de mémoire que
réellement. Mais si c'est un problème pour toi, tu peux utiliser un
ordonanceur qui fasse ce type de travail.


--
__Pascal Bourguignon__ http://www.informatimago.com/
Cats meow out of angst
"Thumbs! If only we had thumbs!
We could break so much!"



Avatar
Pascal Bourguignon
"Rémi Moyen" writes:
La question, c'est comment se comporte new()? Comment est elle
implémentée?


Je sais pas... c'est aussi la question que je me pose (et que je pose
aux gens ici...) !


Oui, ça ça dépend du compilateur C++ et de la bibliothèque run-time utilisée.
Il peut y avoir plusieurs options différentes.

Et un programme utilisateur peut fournir sa propre version de new/delete.

Si tu utilises g++, tu peux facilement jetter un oeuil aux sources de new/delete.

--
__Pascal Bourguignon__ http://www.informatimago.com/
Cats meow out of angst
"Thumbs! If only we had thumbs!
We could break so much!"


Avatar
Marwan Burelle
On Fri, 16 Feb 2007 17:30:56 +0000 (UTC)
(Michel Talon) wrote:

Oui. Tu seras mort pour rien. Voilà pourquoi il faut *beaucoup* de
swap même si on ne compte pas en utiliser beaucoup. L'argument, le
swap est lent, si on utilise le swap on va ramer, etc. ne vaut rien.
Le swap est une sécurité pour le cas où l'overcommit partirait en
vrille. Ca ne veut pas dire qu'on va l'utiliser beaucoup dans l'usage
courant.


Même sans overcommit il faut du swap. On utilise de la VM pour avoir
plus d'espace mémoire que dans la mémoire physique. Et un bon système
envoies se balader en swap les pages inactives (notamment pour faire de
la place aux caches.)

--
Ca parait mieux que ma table de calssification periodique des OS.
-+- GV in GFA : "Tous des fossiles, on vous dit !" -+-

Avatar
Rémi Moyen
On Feb 16, 6:27 pm, Pascal Bourguignon wrote:
"Rémi Moyen" writes:
La question, c'est comment se comporte new()? Comment est elle
implémentée?


Je sais pas... c'est aussi la question que je me pose (et que je pose
aux gens ici...) !


Oui, ça ça dépend du compilateur C++ et de la bibliothèque run-ti me utilisée.
Il peut y avoir plusieurs options différentes.

Et un programme utilisateur peut fournir sa propre version de new/delete.


En l'occurrence, le new() de la stdlibc++ fournie avec g++ me
suffirait largement.

Si tu utilises g++, tu peux facilement jetter un oeuil aux sources de new /delete.


Mouais... Je ne m'épargnerais pas de plonger dans ce code, hein ? :-/
--
Rémi Moyen



Avatar
Mehdi BENKIR
On Feb 16, 10:46 am, (Michel Talon) wrote:
Rémi Moyen wrote:


Hmmm... Je crois que je me suis mal exprimé, ou alors que je ne
comprends pas exactement ta réponse. Si je te comprends bien, tu dis
que dans certains cas, 'new' pourrait réussir alors qu'en réalité, le
jour où j'essaierais d'écrire dans ma mémoire, ça ne marchera pas
parce qu'en réalité le noyau m'a alloué des pages qui ne sont pas
vraiment disponibles ? Si c'est le cas, c'est en effet plutôt (!)
génant.


Sur un système multi-utilisateurs, permettre à n'importe quel processus
d'allouer une quantité arbitraire de mémoire reviendrait à permettre au
premier processus venu d'empêcher les autres d'accéder par la suite aux
ressources. Y compris, par exemple, au point de rendre le noyau
incapable de créer de nouveaux processus.

Pour éviter cela, même lorsque le langage de programmation utilisé
permet la réservation arbitraire d'une taille arbitraire de mémoire, le
système se contente de prendre bonne note et enchaine (et donc, rend
rapidement la main).

Si on considère que, même si la mémoire avait été allouée, un incident
matériel aurait pu faire en sorte qu'elle devienne indisponible entre le
moment où elle aura été réservée et le moment où elle est effectivement
utilisée, on comprend que, pour toute opération d'entrée/sortie, y
compris en mémoire, le programmeur doit envisager l'échec de toute
opération de lecture ou d'écriture.


1 2 3 4 5