OVH Cloud OVH Cloud

question technique sur l'opérateur d'allocation new

29 réponses
Avatar
Frédéric GOURUL
Bonjour,

Je me pose pas mal de questions sur l'implémentation de l'opérateur
d'allocation mémoire void* ::operator new(size_t), en particulier s'il
contient des optimisations pour l'allocation des small-blocks. Si quelqu'un
pouvait éclairer ma lanterne ou m'indiquer un lien sur le sujet, je suis
preneur. En gros, je voudrais savoir si ca a un sens de créer un système qui
ferait de la pré-allocation de mémoire pour les objets de petite taille...

merci.

10 réponses

1 2 3
Avatar
Pierre Maurette
Ça dépend de l'application et du système. Ça m'est arrivé de le faire
une fois -- pour une accelération de l'application par une facteur de
300. Mais dans l'ensemble, la plupart (sinon tous) les implémentations
de new renvoient à malloc,
Il me semble qu'il y a une logique : new() , élément du langage, a un niveau

d'abstraction élevé. Au contraire de malloc(), qui fait partie de la bib
standard. En fait, ce que je veux dire, c'est que si j'avais à implémenter
new(), je le ferais de cette façon.
Pierre

Avatar
kanze
Marc Boyer wrote in message
news:<bq7aj5$las$...
Frédéric GOURUL wrote:

Je me pose pas mal de questions sur l'implémentation de l'opérateur
d'allocation mémoire void* ::operator new(size_t)


L'implémentation de new est dépendante de ton compilateur.
Beaucoup de littérature a été écrite sur de mauvaises performances
pour les blocs de petites tailles. La dernière fois que j'ai rapporté
cette affirmation, Gabriel Dos m'a expliqué que c'était n'importe
quoi. Le sujet ne m'a jamais intéressé au point que je fasse des
mesures.


(C'est Gaby, Gabriel ou M. Dos Reis, je crois, mais pas Gabriel Dos:-).)

Ça dépend de l'implémentation ; je suis sûr que c'est possible à écrire
un malloc qui est très inefficace quand on alloue beaucoup de petits
blocs. Je suis même sûr qu'un en a existé réelement, parce que j'ai eu
le problème (mésures à l'appui) sous Sun OS 4.

Mais c'est d'un autre époque. On sait faire mieux, et j'ai du mal à
imaginer un système aujourd'hui qui ne vient pas avec plusieurs
implémentations de malloc, chacune optimisées pour une utilisation
différente, ni que l'implémentation par défaut n'ait pas une performance
« convenable » pour la plupart des applications. (Avec la taille des
mémoires aujourd'hui, ce n'est pas rare que les différents algorithmes
se trouvent tous derrière le même malloc, et qu'on les choisit avec des
fonctions supplémentaires non standard.)

Par contre, ce qui paraît évident, c'est qu'un opérateur qui prend
n'importe quelle taille sera moins efficace qu'un opérateur qui ne
gère qu'une seule taille. Donc, si toi programmeur tu sais que tu vas
allouer beaucoup d'objets de taille fixe N
(N=sizeof(TaClasseSupeUtilisee)), il peut être intéressant de
particulariser l'opérateur pour cet objet là.


En théorie. Dans la pratique, il existe des implémentations de malloc
qui le font déjà pour vous, internalement. Sur ma machine, par exemple,
j'ai trois malloc, choisi au moment de l'édition des liens, et un a une
option M_MXFAST qui dit :

Set maxfast to value. The algorithm allocates all
blocks below the size of maxfast in large groups and
then doles them out very quickly. The default value
for maxfast is 24.

Il y a néanmoins un petit problème : on n'a pas droit de changer les
paramètres après la première allocation d'un petit bloc. Ce qui pose un
problème en C++, en ce qu'il y a des allocations dans les constructeurs
des objets statiques.

En gros, je voudrais savoir si ca a un sens de créer un système qui
ferait de la pré-allocation de mémoire pour les objets de petite
taille...


De toute façon, faire de la pré-allocation pour des objets de grosse
taille, c'est difficile.


De toute façon, si tu t'amuses à allouer des millions d'objets de grosse
taille, ton programme n'ira pas loin.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Gabriel Dos Reis
Marc Boyer writes:

| Frédéric GOURUL wrote:
| > Je me pose pas mal de questions sur l'implémentation de l'opérateur
| > d'allocation mémoire void* ::operator new(size_t)
|
| L'implémentation de new est dépendante de ton compilateur.
| Beaucoup de littérature a été écrite sur de mauvaises performances
| pour les blocs de petites tailles. La dernière fois que j'ai
| rapporté cette affirmation, Gabriel Dos m'a expliqué que c'était
| n'importe quoi. Le sujet ne m'a jamais intéressé au point que
| je fasse des mesures.

Mon nom de famille est « Dos Reis », en deux mots sans trait d'union.
(je ne pensais pas devoir le dire un jour ici).

Exactement sur quelle affirmation étais-je censé avoir expliqué que tu
rapportais n'importe quoi ? Il y a beaucoup de mythes autour de
operator new, il y a en aussi qui en sont moins mythes.


-- Gaby
Avatar
Marc Boyer
Gabriel Dos Reis wrote:
Marc Boyer writes:

Mon nom de famille est « Dos Reis », en deux mots sans trait d'union.
(je ne pensais pas devoir le dire un jour ici).


Mea culpa.

Exactement sur quelle affirmation étais-je censé avoir expliqué que tu
rapportais n'importe quoi ? Il y a beaucoup de mythes autour de
operator new, il y a en aussi qui en sont moins mythes.


"For occult reasons, the default allocator is notoriously slow.
A possible reason is that it is usually implemented as a thin wrapper
around the C heap allocator (malloc/realloc/free). The C heap
allocator is not focused on optimizing small chunk allocations."
Modern C++ Design, $4.1, p78

Marc Boyer
--
Lying for having sex or lying for making war? Trust US presidents :-(

Avatar
Gabriel Dos Reis
"Pierre Maurette" <mmaauurreettttttee.ppiieerrrree@@ffrreeee.ffrr> writes:

| > Ça dépend de l'application et du système. Ça m'est arrivé de le faire
| > une fois -- pour une accelération de l'application par une facteur de
| > 300. Mais dans l'ensemble, la plupart (sinon tous) les implémentations
| > de new renvoient à malloc,
| Il me semble qu'il y a une logique : new() , élément du langage, a un niveau
| d'abstraction élevé. Au contraire de malloc(), qui fait partie de la bib
| standard. En fait, ce que je veux dire, c'est que si j'avais à implémenter
| new(), je le ferais de cette façon.

C'est curieux, si je devais implémenter, j'implementerais la
bibliothèque en termes d'élément du langage ; et j'aurais tendance à
dire que les éléments de la bibliothèque ont un niveau d'abstraction
plus élevé que celui des éléments du langage. Mais bon, ta description
n'est pas correcte.

Il faut distinguer ce qu'on appelle une « expression de new »
(ou « new-expression » en anglo-saxon) de la fonction globale appelée
« operator new » (qui fait bien partie de la bibliothèque). Je ne vais
pas refaire un cours sur ces deux notions que Scott Meyers explique
bien dans l'un de ses trois classiques (désolé je n'ai pas la
référence sous la main). La différence fondamentale est qu'une
expression de new finit par appeler une fonction « operator new » ;
cette fonction peut être une surchage ou un remplacement de la
fonction définie par la bibliothèque.

La fonction globale « ::operator new » est plus flexible que
std::malloc parce que :
(1) elle remplaçable (à l'inverse de std::malloc)
(2) elle est surchargeable (pour tenir compte d'autres paramètres);
(3) elle est plus liée au langage que std::malloc ne l'est.

Par (1), je veux dire que l'utilisateur peut fournir sa propre
définition, celle-ci prendra précédence sur celle livrée avec le
compilateur. Cela n'est pas de la surchage. (On peut surcharger
operator new, mais ce que je viens de décrire n'est pas de la
surcharge.)

J'entends souvent dire que ::operator new doit être plus lente que
std::malloc. Cette affirmation est souvent soit un mythe urbain ou une
observation d'une implémentation de mauvaises qualité -- il en existe
malheureusement, les fabriquants de compilateurs disant souvent que si
un utilisateur a réellement besoin d'un allocateur plus performant,
il peut toujours implémenter le sien :-( -- mais comme pour n'importe
quelle fonctionnalité si on utilise un mauvais compilateur on a un
mauvais résultat. Donc.

Un point à faire remarquer est que certains fabriquants de
compilateurs, pour une raison X ou Y, insistent à vouloir implémenter
la bibliothèque C++ en termes de la bibliothèque C. (D'un point de vue
de conception, efficicaté, et robustesse, l'inverse a beaucoup plus de
sens, mais passons). En particulier, certains fournissent la
définition par défaut de ::operator new(size_t) comme un simple appel
à std::malloc, mais cela n'est pas une exigence de la norme C++ --
c'est juste qu'ils trouvent que c'est une implémentation à moindre frais.
Cela n'est pas parce que « ::operator new aurait un niveau
d'abstraction plus élevé que std::malloc ».

-- Gaby
Avatar
Gabriel Dos Reis
Marc Boyer writes:

| > Exactement sur quelle affirmation étais-je censé avoir expliqué que tu
| > rapportais n'importe quoi ? Il y a beaucoup de mythes autour de
| > operator new, il y a en aussi qui en sont moins mythes.
|
| "For occult reasons, the default allocator is notoriously slow.

oui, je maintiens alors que c'est n'importe quoi.

-- Gaby
Avatar
Pierre Maurette
"Gabriel Dos Reis" a écrit ...
[...]
d'abstraction plus élevé que std::malloc ».
Merci de votre long message qui me donne du grain à moudre.

Par exemple, la différence langage/bibliothèques standards a été claire,
elle l'est aujourd'hui un peu moins.
Je m'intéresse un peu à l'allocation dans une optique d'alignement. Non
portable bien entendu, par exemple une zone alignée sur un multiple de 16 en
ia32. Avec des accès par pointeurs et malloc(), je sais faire, mais ce n'est
pas très beau, pas assez transparent.
Auriez-vous des sugestions à ce sujet ?
Pierre

Avatar
James Kanze
Gabriel Dos Reis writes:

|> La fonction globale « ::operator new » est plus flexible que
|> std::malloc parce que :
|> (1) elle remplaçable (à l'inverse de std::malloc)
|> (2) elle est surchargeable (pour tenir compte d'autres paramètres);
|> (3) elle est plus liée au langage que std::malloc ne l'est.

|> Par (1), je veux dire que l'utilisateur peut fournir sa propre
|> définition, celle-ci prendra précédence sur celle livrée
|> avec le compilateur. Cela n'est pas de la surchage. (On peut
|> surcharger operator new, mais ce que je viens de décrire n'est
|> pas de la surcharge.)

Juste un détail : la différence est plutôt que la norme ne
garantit pas qu'on peut remplacer malloc. Dans la pratique, je ne
connais pas d'implémentation où on ne peut pas, et en fait, je le
fais assez régulièrement (chaque fois que j'utilise Purify, pour
commencer).

|> J'entends souvent dire que ::operator new doit être plus lente
|> que std::malloc. Cette affirmation est souvent soit un mythe urbain
|> ou une observation d'une implémentation de mauvaises qualité
|> -- il en existe malheureusement, les fabriquants de compilateurs
|> disant souvent que si un utilisateur a réellement besoin d'un
|> allocateur plus performant, il peut toujours implémenter le sien
|> :-( -- mais comme pour n'importe quelle fonctionnalité si on
|> utilise un mauvais compilateur on a un mauvais résultat. Donc.

Dans toutes les implémentations que j'ai régardé, la fonction
operator new ne fait qu'appeler malloc directement. Elle n'est donc ni
plus rapide, ni moins rapide. Dans une bonne implémentation, il y
aurait un choix de malloc, chaqu'un optimisé pour des utilisations
différentes.

En ce qui concerne la vitesse d'allocation de beaucoup de petits blocs,
j'ai réelement eu le problème, mais il y a longtemps. Je ne sais
pas où on en est aujourd'hui. N'empêche qu'on l'aime ou non,
beaucoup d'entre nous sont obligés pour une raison ou une autre à
utiliser des implémentations dont la qualité n'est pas tout ce
qu'on voudrait. Savoir des moyens de contourner telle ou telle faiblesse
de qualité, ça fait partie du bagage de l'ingenieur.

|> Un point à faire remarquer est que certains fabriquants de
|> compilateurs, pour une raison X ou Y, insistent à vouloir
|> implémenter la bibliothèque C++ en termes de la
|> bibliothèque C. (D'un point de vue de conception, efficicaté,
|> et robustesse, l'inverse a beaucoup plus de sens, mais passons). En
|> particulier, certains fournissent la définition par défaut de
|> ::operator new(size_t) comme un simple appel à std::malloc, mais
|> cela n'est pas une exigence de la norme C++ -- c'est juste qu'ils
|> trouvent que c'est une implémentation à moindre frais. Cela
|> n'est pas parce que « ::operator new aurait un niveau
|> d'abstraction plus élevé que std::malloc ».

Dans le cas de l'operator new, ce n'est pas une exigeance de la norme,
mais la norme interdit bien d'implémenter malloc en termes de
operator new. Or, si on ne veut pas maintenir deux codes quasiment
identiques, je ne vois pas d'autre solution que d'implémenter
operator new en termes de malloc.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93
Avatar
Gabriel Dos Reis
James Kanze writes:

| Gabriel Dos Reis writes:
|
| |> La fonction globale « ::operator new » est plus flexible que
| |> std::malloc parce que :
| |> (1) elle remplaçable (à l'inverse de std::malloc)
| |> (2) elle est surchargeable (pour tenir compte d'autres paramètres);
| |> (3) elle est plus liée au langage que std::malloc ne l'est.
|
| |> Par (1), je veux dire que l'utilisateur peut fournir sa propre
| |> définition, celle-ci prendra précédence sur celle livrée
| |> avec le compilateur. Cela n'est pas de la surchage. (On peut
| |> surcharger operator new, mais ce que je viens de décrire n'est
| |> pas de la surcharge.)
|
| Juste un détail : la différence est plutôt que la norme ne
| garantit pas qu'on peut remplacer malloc.

sûr, mais si tu remplaces, tu as ce que tu méritesd, de la même
manière que si tu déréfenreces un pointeur nul tu auras ce que tu
mérites.

| Dans la pratique, je ne
| connais pas d'implémentation où on ne peut pas,

ce qui ne veut pas dire que cela n'arrivera jamais.

En fait, l'une des prochaines versions de GCC (je ne sais pas si c'est
3.5 ou 4.0) va probablement changer cela. Certains voient des
optimisations qu'ils peuvent faire et veulent l'implémenter. J'ai
argué pour qu'on ne le fasse pas, mais visiblement j'étais
minoritaire.

[...]

| En ce qui concerne la vitesse d'allocation de beaucoup de petits blocs,
| j'ai réelement eu le problème, mais il y a longtemps. Je ne sais
| pas où on en est aujourd'hui. N'empêche qu'on l'aime ou non,
| beaucoup d'entre nous sont obligés pour une raison ou une autre à
| utiliser des implémentations dont la qualité n'est pas tout ce
| qu'on voudrait. Savoir des moyens de contourner telle ou telle faiblesse
| de qualité, ça fait partie du bagage de l'ingenieur.

certainement. Mais je suis moins sûr que cela fasse partie du bagage
de l'ingénieur de *ne pas* savoir des défauts de fabrication.

| |> Un point à faire remarquer est que certains fabriquants de
| |> compilateurs, pour une raison X ou Y, insistent à vouloir
| |> implémenter la bibliothèque C++ en termes de la
| |> bibliothèque C. (D'un point de vue de conception, efficicaté,
| |> et robustesse, l'inverse a beaucoup plus de sens, mais passons). En
| |> particulier, certains fournissent la définition par défaut de
| |> ::operator new(size_t) comme un simple appel à std::malloc, mais
| |> cela n'est pas une exigence de la norme C++ -- c'est juste qu'ils
| |> trouvent que c'est une implémentation à moindre frais. Cela
| |> n'est pas parce que « ::operator new aurait un niveau
| |> d'abstraction plus élevé que std::malloc ».
|
| Dans le cas de l'operator new, ce n'est pas une exigeance de la norme,
| mais la norme interdit bien d'implémenter malloc en termes de
| operator new.

C'est une exigence ou non ? choisis ton camp camarade.

(accessoirement, je ne propose pas d'implémenter malloc en termes de
operator new.)

| Or, si on ne veut pas maintenir deux codes quasiment
| identiques, je ne vois pas d'autre solution que d'implémenter
| operator new en termes de malloc.

D'abord, focaliser uniquement sur le code est un bon moyen de ne pas
delivrer une implémentation de qualité. C'est ce qui arrive dans pas
mal de cas, en commençant par le chapitre 14. C'est une très mayvaise
chose ; malheureusement beaucoup de gens le font, même les ingénieurs
avec un gros bagage. Ensuite, et pour moi c'est le plus important, il faut
considérer les conditions d'utilisations. Dans mon design
d'implémentation, la définition par défaut de operator new et la
définition de malloc renvoient à la même fonction -- derrière la
scène. Elle ne sont pas implémentées en termes l'une de l'autre.

De fait, le « quasiment » fait la différence.

-- Gaby
Avatar
James Kanze
Gabriel Dos Reis writes:

|> James Kanze writes:

|> | Gabriel Dos Reis writes:

|> | |> La fonction globale « ::operator new » est plus
|> | |> flexible que std::malloc parce que :
|> | |> (1) elle remplaçable (à l'inverse de std::malloc)
|> | |> (2) elle est surchargeable (pour tenir compte d'autres
|> | |> paramètres);
|> | |> (3) elle est plus liée au langage que std::malloc ne l'est.

|> | |> Par (1), je veux dire que l'utilisateur peut fournir sa propre
|> | |> définition, celle-ci prendra précédence sur celle
|> | |> livrée avec le compilateur. Cela n'est pas de la surchage.
|> | |> (On peut surcharger operator new, mais ce que je viens de
|> | |> décrire n'est pas de la surcharge.)

|> | Juste un détail : la différence est plutôt que la norme
|> | ne garantit pas qu'on peut remplacer malloc.

|> sûr, mais si tu remplaces, tu as ce que tu méritesd, de la
|> même manière que si tu déréfenreces un pointeur nul tu
|> auras ce que tu mérites.

De même que si tu utilises des threads, ou des sockets, ou une
interface graphique, n'est-ce pas ? Après tout, aucun n'est garanti
par la norme, et dans chaque cas, tu comptes sur des comportements
définis par l'implémentation.

Exactement comme dans ces cas, les implémentations garantissent le
comportement du remplacement de malloc, et en offrent même plusieurs
pour que tu n'as pas besoin d'en écrire un toi-même.

|> | Dans la pratique, je ne
|> | connais pas d'implémentation où on ne peut pas,

|> ce qui ne veut pas dire que cela n'arrivera jamais.

Certes. Mais on parle ici des questions d'optimisation ou de mise au
point, et non de quelque chose de fondamental dans la fonctionnement du
programme. La risque pour la portabilité est en fait moins grande que
si tu utilises des threads.

|> En fait, l'une des prochaines versions de GCC (je ne sais pas si
|> c'est 3.5 ou 4.0) va probablement changer cela. Certains voient des
|> optimisations qu'ils peuvent faire et veulent l'implémenter. J'ai
|> argué pour qu'on ne le fasse pas, mais visiblement j'étais
|> minoritaire.

Il vont faire en sort qu'on ne peut pas remplacer malloc par sa propre
version ? Or que « man malloc », au moins sous Solaris, dit
expressemment qu'on peut, et qu'on a le choix entre plusieurs versions.
(En fait, les mallocs de Sun vont plus loins, en ce qu'ils offrent des
sémantiques différentes, au moins dans le cas de BSD malloc.)

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93
1 2 3