Analyse de l'utilisation de la mémoire: Java vs C ++ Négligeable?

9

Comment l'utilisation de la mémoire d'un objet entier écrit en Java se compare \ contraste avec l'utilisation de la mémoire d'un objet entier écrit en C ++? La différence est-elle négligeable? Aucune différence? Une grosse différence? Je suppose que c'est la même chose car un int est un int quelle que soit la langue (?)

La raison pour laquelle j'ai posé cette question est parce que je lisais sur l'importance de savoir quand les besoins en mémoire d'un programme empêcheront le programmeur de résoudre un problème donné.

Ce qui m'a fasciné, c'est la quantité de mémoire requise pour créer un seul objet Java. Prenons par exemple un objet entier. Corrigez-moi si je me trompe mais qu'un objet entier Java nécessite 24 octets de mémoire:

  • 4 octets pour sa variable d'instance int
  • 16 octets de surcharge (référence à la classe de l'objet, aux informations de récupération de place et aux informations de synchronisation)
  • 4 octets de remplissage

Comme autre exemple, un tableau Java (qui est implémenté en tant qu'objet) nécessite 48 octets et plus:

  • 24 octets d'informations d'en-tête
  • 16 octets de surcharge d'objet
  • 4 octets de longueur
  • 4 octets pour le remplissage
  • plus la mémoire nécessaire pour stocker les valeurs

Comment ces utilisations de la mémoire se comparent-elles au même code écrit en C ++?

J'étais inconscient de l'utilisation de la mémoire des programmes C ++ et Java que j'ai écrits, mais maintenant que je commence à apprendre les algorithmes, j'apprécie davantage les ressources de l'ordinateur.

Anthony
la source
6
Qu'est-ce qu'un "objet entier" en C ++? int? Si c'est le cas, vous devriez comparer cela à intJava, pas Integer- tant que vos entrées C ++ sont 32 bits.
Mat
+1 Si je crée une classe c ++ qui n'a qu'une seule variable int, puis l'instancie
Anthony
3
un int n'est pas un int - il dépend de la plate-forme
1
Un C ++ avec un seul membre int n'aura généralement pas de surcharge. Il utilisera exactement autant d'espace que la plate-forme utilise pour stocker un int (généralement 4 octets sur les plates-formes PC actuelles).
Dirk Holsopple
+1 pour un programmeur Java curieux de mémoire. Plus que toute autre chose, la conscience de la mémoire est le facteur le plus important déterminant les performances des architectures modernes.
imallett

Réponses:

15

Cela dépend de la plate-forme et de la mise en œuvre.

C ++ garantit que la taille de charest exactement d'un octet et d'au moins 8 bits de large. Ensuite, la taille de a short intest d'au moins 16 bits et pas inférieure à char. La taille d'un intest au moins aussi grande que la taille de short int. La taille de long intest d'au moins 32 bits et pas inférieure à int.

sizeof(char) == 1; sizeof(long int) >= sizeof(int) >= sizeof(short int) >= sizeof(bool) >= sizeof(char).

Le modèle de mémoire réel de C ++ est cependant très compact et prévisible . Par exemple, il n'y a pas de métadonnées dans les objets, les tableaux ou les pointeurs. Les structures et les classes sont contiguës, tout comme les tableaux, mais le remplissage peut être placé là où c'est nécessaire et nécessaire.

Franchement, une telle comparaison est au mieux idiote car l'utilisation de la mémoire Java dépend plus de l'implémentation Java que du code qu'elle exécute.

zxcdw
la source
1
Certes, mais à des fins de comparaison, je dirais que nous devrions supposer des types entiers de taille équivalente (même s'ils ne sont disponibles que sous des noms différents). Après tout, des tailles différentes signifient une sémantique différente, et sur de nombreuses plates-formes communes (pas toutes), les tailles sont identiques, ou des nombres entiers de la même taille sont disponibles sous des noms différents.
Remarque à l'OP: Vous feriez mieux de choisir vous-même la taille de l'entier - si vous voulez un int 32 bits en C ++, vous pouvez utiliser int32_t.
K.Steff
9

La plupart des réponses semblent ignorer quelques points assez importants.

Tout d'abord, dans un très grand nombre de Java, vous ne voyez pratiquement jamais un brut int- presque toute utilisation est de Integer, donc le fait qu'un intpourrait être (environ) de la même taille qu'un inten C ou C ++ est presque hors de propos, sauf pour cela ( d'après mon expérience, petit) pourcentage de code qui utilise uniquement intau lieu de Integer.

Deuxièmement, la taille des objets individuels n'a presque rien à voir avec l'empreinte mémoire d'un programme dans son ensemble. En Java, l'empreinte mémoire du programme se rapporte principalement à la façon dont le garbage collector a été réglé. Dans la plupart des cas, le GC est réglé pour maximiser la vitesse, ce qui signifie (en grande partie) d'exécuter le GC aussi rarement que possible.

Je n'ai pas de lien à portée de main pour le moment, mais il y a eu des tests montrant que Java peut fonctionner à la même vitesse que C, mais pour ce faire, vous devez exécuter le GC assez rarement qu'il utilise environ 7 fois plus Mémoire. Ce n'est pas parce que les objets individuels sont 7 fois plus gros, mais parce que le GC peut devenir assez cher si vous le faites trop souvent. Pire, GC ne peut libérer de la mémoire que lorsqu'il peut "prouver" qu'il n'y a plus aucun moyen d'accéder à un objet, plutôt que simplement lorsque vous savez que vous avez fini de l'utiliser. Cela signifie que même si vous exécutez le GC beaucoup plus souvent pour minimiser l'utilisation de la mémoire, vous pouvez probablement prévoir qu'un programme typique ait une empreinte mémoire plus grande. Dans un cas comme celui-ci, vous pouvez réduire le facteur à 2 ou 3 au lieu de 7. Cependant, même si vous allez trop loin,1 .

Selon la situation, un autre facteur peut ou non être significatif: la mémoire occupée par la JVM elle-même. Ceci est plus ou moins fixe, donc en pourcentage, il peut être énorme si l'application n'a pas besoin de beaucoup de stockage, ou minuscule si l'application a besoin de stocker beaucoup. Au moins sur ma machine, même l'application Java la plus triviale semble occuper quelque chose comme 20-25 mégaoctets (pourrait être plus de 1000 fois pour les programmes triviaux, ou presque incommensurablement minuscule pour les gros).


1 Cela ne veut pas dire que personne ne pourrait réussir à écrire Java avec une empreinte proche de ce que vous obtiendriez avec C ++. Il est seulement de dire que tout ayant le même nombre / taille des objets, et le fonctionnement du GC va vraiment pas souvent vous y en règle.

Jerry Coffin
la source
7
En ce qui concerne votre premier point: je ne suis pas un gars Java, mais les API Java que j'ai vues n'ont jamais utilisé Integer(pourquoi le feraient-ils?) À la place int. Seules les collections génériques n'ont pas d'autre choix que d'utiliser en Integerraison de l'effacement des types, mais si vous vous en souciez, vous pouvez les remplacer par une implémentation spécialisée intou quel que soit le type primitif dont vous avez besoin. Et puis il y a la boxe temporaire pour passer par du code générique d'encapsulation (par exemple tout ce qui nécessite un Object[]). En dehors de cela, avez-vous des sources pour la surcharge de l'espace GC? Je n'en doute pas vraiment, je suis simplement curieux.
9

J'espère que vous vous rendez compte que tout cela est profondément défini par l'implémentation, à la fois pour Java et C ++. Cela étant dit, le modèle objet de Java nécessite un peu d'espace.

Les objets C ++ n'ont (généralement) besoin d' aucun stockage sauf ceux dont les membres ont besoin. Notez que (contrairement à Java, où tout ce qui est défini par l'utilisateur est un type de référence), le code client peut utiliser un objet à la fois comme type de valeur ou comme type de référence, c'est-à-dire qu'un objet peut stocker un pointeur / référence vers un autre objet, ou stocker directement l'objet sans indirection. Un pointeur supplémentaire par objet est nécessaire s'il existe des virtualméthodes, mais plusieurs classes utiles sont conçues pour s'entendre sans polymorphisme et n'en ont pas besoin. Il n'y a pas de métadonnées GC ni de verrouillage par objet. Ainsi, les class IntWrapper { int x; public: IntWrapper(int); ... };objets n'ont pas besoin de plus d'espace que les ints simples et peuvent être placés directement (c'est-à-dire sans indirection) dans les collections et autres objets.

Les tableaux sont délicats simplement parce qu'il n'y a pas d'équivalent prédéfini et commun à un tableau Java en C ++. Vous pouvez simplement allouer un tas d'objets avec new[](sans aucune surcharge / métadonnée) mais il n'y a pas de champ de longueur - l'implémentation en stocke probablement un mais vous ne pouvez pas y accéder. std::vectorest un tableau dynamique et a donc une surcharge supplémentaire et une interface plus grande. std::arrayet les tableaux de style C (int arr[N];), ont besoin d'une constante de temps de compilation. En théorie, cela devrait être juste le stockage de l'objet plus un seul entier pour la longueur - mais comme vous pouvez obtenir un redimensionnement dynamique et une interface complète avec très peu d'espace supplémentaire, vous optez simplement pour cela dans la pratique. Notez que tous ceux-ci, ainsi que toutes les autres collections, stockent par défaut les objets par valeur, vous économisant ainsi l'indirection et l'espace pour les références, et améliorant le comportement du cache. Vous devez explicitement stocker des pointeurs (intelligents, s'il vous plaît) pour obtenir l'indirection.

Les comparaisons ci-dessus ne sont pas entièrement justes, car certaines de ces économies sont réalisées en n'incluant pas les fonctionnalités incluses par Java, et leur équivalent C ++ est souvent moins optimisé que l'équivalent Java (*). La manière courante d'implémenter virtualen C ++ impose exactement autant de frais généraux que la manière courante d'implémenter virtualen Java. Pour obtenir un verrou, vous avez besoin d'un objet mutex complet, qui est probablement plus grand que quelques bits. Pour obtenir le comptage des références ( paséquivalent à GC, et ne doit pas être utilisé comme tel, mais parfois utile), vous avez besoin d'un pointeur intelligent qui ajoute un champ de comptage de référence. À moins que l'objet ne soit construit avec soin, le nombre de références, l'objet pointeur intelligent et l'objet référencé sont dans des emplacements complètement séparés, et même lorsque vous le construisez correctement, le pointeur partagé peut (doit?) Toujours être deux pointeurs au lieu d'un. Là encore, un bon style C ++ n'utilise pas suffisamment ces fonctionnalités pour que cela ait de l'importance - en pratique, les objets d'une bibliothèque C ++ bien écrits en utilisent moins. Cela ne signifie pas nécessairement moins d'utilisation de la mémoire dans l'ensemble, mais cela signifie que C ++ a une bonne longueur d'avance à cet égard.

(*) Par exemple, vous pouvez obtenir des appels virtuels, des codes de hachage d'identité et un verrouillage avec un seul mot pour certains objets (et deux mots pour de nombreux autres objets) en fusionnant les informations de type avec divers indicateurs et en supprimant les bits de verrouillage pour les objets qui sont peu susceptible d'avoir besoin de verrous. Voir Implémentation efficace en temps et en espace du modèle objet Java (PDF) par David F. Bacon, Stephen J. Fink et David Grove pour une explication détaillée de cette optimisation et d'autres.


la source
3

Un plain int, en java, prend exactement autant d'espace qu'un inten C ++, à condition que les deux implémentations utilisent la même taille entière et le même alignement de mémoire.

Un «objet» int (un entier encadré , c'est-à-dire une instance de classe Integer), transporte tous les frais généraux d'une instance de classe en Java, il est donc beaucoup plus volumineux qu'un inten C ++. Cependant, si vous deviez équiper un objet en C ++ avec les mêmes fonctionnalités que les objets Java fournis avec le prêt à l'emploi (polymorphisme, boxe, garbage collection, RTTI), vous vous retrouveriez probablement avec un objet d'égale Taille.

Et puis il y a des considérations d'optimisation; étant donné que les modèles d'exécution et les paradigmes de programmation diffèrent, il est peu probable qu'un problème non trivial soit résolu de la même manière dans les deux langages, donc comparer la taille du stockage à ce niveau n'a pas beaucoup de sens.

Oui, les objets Java entraînent plus de surcharge par défaut que les classes C ++, mais ils viennent avec plus de fonctionnalités, et cela conduit à un style de programmation différent - un bon programmeur peut tirer parti des avantages et des inconvénients de l'un ou l'autre langage.

tdammers
la source
+1 Plus de frais généraux mais plus de fonctionnalités en Java, je comprends maintenant, merci
Anthony