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

Problème d'allocation

15 réponses
Avatar
JKB
Bonjour à tous,

Je suis parfaitement débutant en C++ (mais j'essaye de faire des
efforts). Je parle courament C et Fortran depuis de nombreuses
années...

J'utilise à partir d'un programme C/Fortran la libboost. Depuis que
j'ai porté ce code sous Solaris, j'ai des problèmes de mémoire
(free() sous Solaris rend la mémoire à l'application et non au
système, ce qui peut poser des problèmes...).

Je fais donc la course au gaspillage mémoire inutile et surtout,
j'essaye de libérer la mémoire dès qu'elle ne sert plus. Pour ce
faire, je vire autant que faire se peut toute variable statique
inutile, tout couple malloc/free pour les remplacer par des
mmap/munmap (mémoire effectivement libérée par Solaris).

Problème : j'ai un wrapper à la libboost qui a été écrit en C++ et
que j'ai déjà modifié malgré mes faibles compétences C++.
J'ai quelque chose uqi ressemble à une allocation :

static graph_t graph(num_nodes);

J'ai l'impression qu'il s'agit d'une allocation d'un tableau de
structure. num_nodes est un entier tout bête. Dans mon bouquin de
C++, on me dit qu'un tableau est déclaré sous la forme :

static graph_t graph[num_nodes];

Première question : quelle est la différence entre ces deux
syntaxes ?

Deuxième question : je sais utiliser mmap/munmap depuis le C. Est-ce
que je peux utiliser la même chose en C++ ? Sinon, comment faire ?
En fait, il faut absolument que je libère ce truc qui me consomme
700 Mo de mémoire :-(

Cordialement,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.

5 réponses

1 2
Avatar
Marc
JKB wrote:

J'ai l'impression que vous cherchez plutôt une implémentation de
malloc/free utilisant mmap (et munmap) plutôt que sbrk à interposer
devant le malloc de la libc. Ça doit exister, mais je ne sais pas si une
des 15 implémentations de malloc livrées avec solaris le fait.



C'est exactement ça et je n'ai pas trouvé. Je me demande même si ça
existe...



Il y a aussi la possibilité d'écrire un allocateur C++ s'appuyant sur
mmap, et de dire à la classe graph_t de boost de l'utiliser au lieu de
l'allocateur par défaut qui utilise malloc. Si le graphe a une structure
qui n'évolue pas, c'est facile de faire un truc efficace. S'il y a plein
de noeuds du graphes détruits et d'autres reconstruits ensuite, ça va
être plus de travail.
Avatar
JKB
Le 06-10-2008, ? propos de
Re: Problème d'allocation,
Marc Espie ?crivait dans fr.comp.lang.c++ :
In article ,
JKB wrote:
J'ai l'impression que vous cherchez plutôt une implémentation de
malloc/free utilisant mmap (et munmap) plutôt que sbrk à interposer
devant le malloc de la libc. Ça doit exister, mais je ne sais pas si une
des 15 implémentations de malloc livrées avec solaris le fait.



C'est exactement ça et je n'ai pas trouvé. Je me demande même si ça
existe...

JKB



Marrant, en 10 minutes dans le code source de solaris, j'ai trouve une
implementation frustre, mais qui a l'air fonctionnelle mmapmalloc ou un
nom dans ce genre.



Testé aussi avec un programme frustre qui alloue 1 Go de mémoire et
qui fait après juste un free. Même motif, même punition. Et j'ai
bien vérifié que le programme de test était lié avec mapmalloc.

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
James Kanze
On Oct 6, 10:41 pm, Marc wrote:
JKB wrote:
> Je viens de trouver un clear() qui semble vider le graph.
> Je vais de ce pas tester... en espérant que cela rende la
> mémoire au système et pas à l'application...



Il n'y a pas de raison, ça va juste faire un free.



Même pas, puisqu'elle ne change que la taille (size()), et non
la capacité.

C'est l'allocateur qu'il faut changer.



J'ai l'impression que vous cherchez plutôt une implémentation
de malloc/free utilisant mmap (et munmap) plutôt que sbrk à
interposer devant le malloc de la libc. Ça doit exister, mais
je ne sais pas si une des 15 implémentations de malloc livrées
avec solaris le fait.



Beaucoup d'implémentations de malloc utilisent mmap ; je crois
que c'est même le défaut sous Linux. Ce n'est pas pour autant
qu'il rend la mémoire au système. Le problème, c'est que les
implémentations de malloc prend (prèsque toujours) de gros blocs
du système, qu'elles découpent par la suite. Elles ne pourraient
donc rendre la mémoire au système qu'une fois tous les blocs
libérés, ce qui exige une analyse assez coûteuse, et qui
risquerait rarement d'aboutir. S'il veut rend la mémoire au
système, il faudrait bien qu'il le prend directement du système,
sans passer par malloc, afin que chaque bloc soit indépendant.
Il faudrait aussi qu'il étudie un peu comment le système gère la
mémoire : dans les Unix primitifs, par exemple, il fallait
pratiquement que tout le heap soit contigu.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Avatar
espie
In article ,
James Kanze wrote:
Beaucoup d'implémentations de malloc utilisent mmap ; je crois
que c'est même le défaut sous Linux. Ce n'est pas pour autant
qu'il rend la mémoire au système. Le problème, c'est que les
implémentations de malloc prend (prèsque toujours) de gros blocs
du système, qu'elles découpent par la suite. Elles ne pourraient
donc rendre la mémoire au système qu'une fois tous les blocs
libérés, ce qui exige une analyse assez coûteuse, et qui
risquerait rarement d'aboutir. S'il veut rend la mémoire au
système, il faudrait bien qu'il le prend directement du système,
sans passer par malloc, afin que chaque bloc soit indépendant.
Il faudrait aussi qu'il étudie un peu comment le système gère la
mémoire : dans les Unix primitifs, par exemple, il fallait
pratiquement que tout le heap soit contigu.



Sur des Unix que je connais, il y a un seuil, souvent parametrable,
au-dessus duquel malloc est `desactive': ca ne sert plus a rien de faire
des trucs specifiques lorsque l'utilisateur te demande 512M de memoire,
tu te contentes d'allouer, et souvent de liberer derriere.

Par exemple, en jetant un oeil au code d'OpenBSD, je vois que les allocations
superieures a quelques pages memoire sont donnees directement a mmap (et
rendues avec munmap lors d'un free).

Les problemes de contiguite du tas ont conduit a de jolis casse-tetes avec
l'arrivee des bibliotheques partagees (surtout sur des archi ou le code
PIC possede des limitations en offset). Sur les systemes modernes, les
allocations sont randomisees, de facon a limiter la casse sur certains
trous de securite.

Typiquement, toujours sur Open, le systeme fabrique un patchwork de pages
au niveau de malloc, en essayant frequemment de te caler les malloc sur des
frontieres de pages, avec rien juste a cote, pour que ca fasse directement
segfault en cas de debordement de tampon... et il traine egalement quelques
canaris en bordures d'allocation, avec des effets fort sympathiques des
qu'on depasse un peu.

Par certains cotes, c'est un vrai bonheur d'avoir un malloc qui te dit
clairement que tu t'es plante a chaque fois que tu debordes d'une allocation.
Avatar
James Kanze
On Oct 7, 10:05 am, (Marc Espie) wrote:
In article
,
James Kanze wrote:



>Beaucoup d'implémentations de malloc utilisent mmap ; je crois
>que c'est même le défaut sous Linux. Ce n'est pas pour autant
>qu'il rend la mémoire au système. Le problème, c'est que les
>implémentations de malloc prend (prèsque toujours) de gros blocs
>du système, qu'elles découpent par la suite. Elles ne pourraient
>donc rendre la mémoire au système qu'une fois tous les blocs
>libérés, ce qui exige une analyse assez coûteuse, et qui
>risquerait rarement d'aboutir. S'il veut rend la mémoire au
>système, il faudrait bien qu'il le prend directement du système,
>sans passer par malloc, afin que chaque bloc soit indépendant.
>Il faudrait aussi qu'il étudie un peu comment le système gère la
>mémoire : dans les Unix primitifs, par exemple, il fallait
>pratiquement que tout le heap soit contigu.



Sur des Unix que je connais, il y a un seuil, souvent
parametrable, au-dessus duquel malloc est `desactive': ca ne
sert plus a rien de faire des trucs specifiques lorsque
l'utilisateur te demande 512M de memoire, tu te contentes
d'allouer, et souvent de liberer derriere.



Tes informations sont certainement plus récentes que les
miennes. La dernière fois que j'ai travaillé à ce niveau-là,
c'était environ 1988 (sur une Unix dérivée de la version 7) :
pas de mmap, pas de mémoire virtuelle, et le système ne gerait
que 4 ségments par processus.

Je crois que ce qu'on peut conclure, c'est qu'il y a beaucoup de
différentes implémentations possibles, au niveau de la
bibliothèque, et que ça varie énormement.

Enfin, puisque je n'ai jamais eu l'occasion jusqu'ici de
m'essayer aux allocateurs, j'ai voulu voir ce que je pouvais
faire. Ça a l'air de marcher ; en tout cas, top montre moins de
mémoire après le déstructeur d'un vector:

MMapAllocator.hh:
-----------------

#ifndef MMapAllocator_hh_20081007ZnfrkcSgil6aOTq2XidkgOeg
#define MMapAllocator_hh_20081007ZnfrkcSgil6aOTq2XidkgOeg

#include <limits>
#include <cstddef>

namespace MMapAllocatorPrivate {

struct AllocatorImpl
{
static void* allocate( std::size_t size ) ;
static void free( void* address, std::size_t size ) ;
} ;
}

template< typename T >
class MMapAllocator : private MMapAllocatorPrivate::AllocatorImpl
{
typedef MMapAllocatorPrivate::AllocatorImpl
Impl ;

public:
typedef std::size_t size_type ;
typedef std::ptrdiff_t
difference_type ;
typedef T* pointer ;
typedef T const* const_pointer ;
typedef T& reference ;
typedef T const& const_reference ;
typedef T value_type ;
template < typename U >
struct rebind
{
typedef MMapAllocator< U >
other ;
} ;

MMapAllocator() throw()
{
}

MMapAllocator(
MMapAllocator const&other ) throw()
{
}

template< typename U >
MMapAllocator(
MMapAllocator< U > const&
other ) throw()
{
}

~MMapAllocator() throw()
{
}

T* address(
T& object ) const
{
return &object ;
}
T const* address(
T const& object ) const
{
return &object ;
}

T* allocate(
std::size_t size,
void const* ignoredHint =
NULL )
{
return static_cast< T* >( Impl::allocate( size ) ) ;
}
void deallocate(
T* memory,
std::size_t size )
{
Impl::free( memory, size ) ;
}

std::size_t max_size() const throw()
{
return std::numeric_limits< std::size_t >::max() ;
}

void construct( T* where, T const& value )
{
new ( static_cast< void* >( where ) )T( value ) ;
}
void destroy( T* where )
{
where->~T() ;
}
} ;

template<>
class MMapAllocator< void >
{
public:
typedef void* pointer ;
typedef void const* const_pointer ;
typedef void value_type ;
template< typename U >
struct rebind
{
typedef MMapAllocator< U >
other ;
} ;
} ;
#endif

MMapAllocator.cc:
-----------------

#include "mmapAllocator.hh"
#define _POSIX_C_SOURCE 200112L
#include <sys/mman.h>
#include <errno.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <memory>

namespace MMapAllocatorPrivate {

void*
AllocatorImpl::allocate(
std::size_t size )
{
void* result = ::mmap( NULL,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0 ) ;
if ( result == NULL ) {
int error = errno ;
if ( ::getenv( "MMAPALLOCATOR_DEBUG" ) != NULL ) {
std::cerr << "MMapAllocator: anonymous mmap failed,
error was: "
<< ::strerror( error ) << std::endl ;
}
throw std::bad_alloc() ;
}
return result ;
}

void
AllocatorImpl::free(
void* address,
std::size_t size )
{
if ( ::munmap( address, size ) != 0 ) {
int error = errno ;
if ( ::getenv( "MMAPALLOCATOR_DEBUG" ) != NULL ) {
std::cerr << "MMapAllocator: munmap failed: address =
"
<< address << ", size = " << size << ", error was:
"
<< ::strerror( error ) << std::endl ;
}
::abort() ;
}
}
}

Testé (mais vraiment supérficiellement -- il y a probablement
encore pas mal d'erreurs) avec g++ sous Solaris. Aussi, ne
l'essaie pas avec un std::list (ou même sans faire une reserve()
suffisant avec un std::vector).

Et pour laisser un peu le hors sujet (Unix spécifique) : je
vois bien une implémentation générique d'un allocateur
là-dedans, avec un paramètre de policy pour les
allocations/déallocations (ce que j'ai fait dans
MMapAllocatorPrivate::AllocatorImpl).

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
1 2