L'allocation et la libération d'un énorme bloc de mémoire au démarrage "nettoient-elles la mémoire?"

18

Le livre Game Coding Complete, Fourth Edition , chapitre 5 ( Game Initialization and Shutdown ), section Checking Memory contient cet exemple de code intéressant:

bool CheckMemory(const DWORDLONG physicalRAMNeeded, const DWORDLONG virtualRAMNeeded)
{
    MEMORYSTATUSEX status; 
    GlobalMemoryStatusEx(&status);
    if (status.ullTotalPhys < physicalRAMNeeded) 
    {
        // you don’t have enough physical memory. Tell the player to go get a 
        // real computer and give this one to his mother. 
        GCC_ERROR("CheckMemory Failure: Not enough physical memory."); 
        return false;
    }
    // Check for enough free memory.
    if (status.ullAvailVirtual < virtualRAMNeeded) 
    {
        // you don’t have enough virtual memory available.
        // Tell the player to shut down the copy of Visual Studio running in the 
        // background, or whatever seems to be sucking the memory dry. 
        GCC_ERROR("CheckMemory Failure: Not enough virtual memory.");
        return false;
    }

    char *buff = GCC_NEW char[virtualRAMNeeded]; 
    if (buff)
    {
        delete[] buff;
    }
    else
    {
        // even though there is enough memory, it isn't available in one
        // block, which can be critical for games that manage their own memory 
        GCC_ERROR("CheckMemory Failure: Not enough contiguous memory."); 
        return false;
    }
}

Cela soulève quelques questions.

La première partie demande simplement au système d'exploitation (Windows) combien de RAM physique est disponible. La partie curieuse est la deuxième, qui alloue un énorme morceau de mémoire et le libère immédiatement:

char *buff = GCC_NEW char[virtualRAMNeeded]; 
if (buff)
{
    delete[] buff;
}

L'auteur poursuit en expliquant:

... cette fonction alloue et libère immédiatement un énorme bloc de mémoire. Cela a pour effet de faire nettoyer Windows toutes les ordures qui se sont accumulées dans le gestionnaire de mémoire et vérifie que vous pouvez allouer un bloc contigu aussi grand que vous en avez besoin. Si l'appel réussit, vous avez essentiellement exécuté l'équivalent d'une machine Zamboni dans la mémoire de votre système, le préparant pour que votre jeu atteigne la glace ...

Mais j'ai mes réserves là-dessus.

"Nettoyer les déchets qui se sont accumulés dans le gestionnaire de mémoire?" Vraiment? Si le jeu vient de commencer, ne devrait-il pas y avoir d'ordures?

"Vous assurer que vous pouvez allouer un bloc contigu?" Dans le cas très spécifique où vous allez gérer la mémoire vous-même, cela aurait du sens, mais quand même, si vous allouez beaucoup de mémoire dès le départ, vous empêchez pratiquement toute autre application de fonctionner le système pendant que le vôtre est allumé.

De plus, cela ne forcera-t-il pas le système d'exploitation à engager toute cette mémoire et, par conséquent, à expulser beaucoup de mémoire vers l'espace disque d'échange, ce qui ralentira beaucoup le démarrage de votre application?

Est-ce vraiment une bonne pratique?

glampert
la source
3
La plupart des systèmes d'exploitation modernes ne feront rien du tout lorsqu'une application alloue une grande zone de mémoire, ils utilisent une allocation optimiste et ne font rien avant de remplir la mémoire, je ne peux pas imaginer que cela fasse plus que d'être un non-op potentiellement lent
Vality
Est-ce que nettoyer une longue bande de terrain dans la jungle, sculpter une radio, des écouteurs, etc. dans du bois fait atterrir des avions et livrer des fournitures?
Dan Neely
10
Game Coding Complete contient beaucoup de bêtises associées à beaucoup de non-compréhension-C ++ et C-qui-ressemble-un-peu-C ++ (également démontré dans cet exemple, en vérifiant le résultat de operator newpour nullptr), si vous me le permettez dire. La meilleure chose que vous puissiez faire avec ce livre est d'allumer votre cheminée. Bien entendu, l'allocation et la libération d'un grand bloc de mémoire ne "nettoient" pas la mémoire.
Damon
@Damon, je n'ai pas vérifié, mais je soupçonne qu'ils ont au moins surchargé l' newopérateur global pour retourner null au lieu de lancer bad_alloc. S'ils ne l'ont pas fait, alors oui, ce code est encore plus absurde: P
glampert
1
@glampert: Même en supposant que ce soit le cas, il operator deleteest nécessaire de l'accepter nullptret de le traiter comme non opérationnel. Toute surcharge globale qui ne fait pas cela est rompue. Ce qui signifie que c'est absurde dans les deux cas. Tout comme en supposant qu'allouer un énorme bloc de mémoire et le libérer fera "magiquement" quelque chose de bien. Au mieux, cela ne fera aucun mal (très probablement, car les pages ne sont même pas touchées ... sinon il peut échanger certaines pages de votre jeu de travail que vous devrez recharger plus tard).
Damon

Réponses:

12

Une pré-allocation d'une grande partie de la mémoire pourrait faire, si votre ordinateur était faible en RAM, pourrait être de forcer le système d'exploitation à libérer de l'espace supplémentaire en échangeant des parties de l'espace mémoire d'autres programmes sur le disque.

Étant donné qu'un tel échange est généralement une opération très lente qui gèle à peu près votre programme pendant qu'il se produit, il pourrait être avantageux de s'assurer que, si cela se produit de toute façon, cela se produira avant le début de votre jeu plutôt qu'au milieu du gameplay.

Cela dit, forcer un swap-to-disk comme celui-ci n'est pas exactement fiable à 100%:

  • Sur de nombreuses implémentations de mémoire virtuelle, la simple allocation de mémoire ne déclenche aucun échange; au contraire, cela ne se produit que lorsque vous accédez pour la première fois à chaque page de la mémoire que vous avez allouée. (Cela est certainement vrai sur les versions modernes de Linux, avec une surcharge de mémoire activée, comme c'est le cas par défaut; je ne sais pas avec certitude comment les différentes versions de Windows le gèrent.)

    Ainsi, si vous voulez vraiment que ce code "nettoie la mémoire", vous devriez probablement ajouter un memset()appel ou équivalent pour réellement écrire des données dans chaque partie du tableau (ou au moins dans les premiers physicalRAMNeededoctets de celui-ci) avant de le libérer.

  • En outre, forcer le système d'exploitation à échanger d'autres programmes hors de la RAM comme cela ne signifie pas qu'ils y resteront . Windows est un système d'exploitation multitâche, et dès qu'un de ces programmes échangés veut s'exécuter à nouveau et utiliser ses données échangées, il sera à nouveau échangé, ce qui pourrait geler à nouveau votre jeu.

    Ainsi, cette astuce n'est généralement utile que si la RAM est consommée par de gros morceaux de données qui ne sont pas activement consultés (tels que les gros documents laissés ouverts en arrière-plan dans certains logiciels d'édition). Les navigateurs Web ont tendance à être particulièrement problématiques à cet égard, car, même si vous n'utilisez pas réellement une page Web ouverte dans un onglet d'arrière-plan, la page peut toujours contenir des scripts qui la forcent à être conservée en RAM.

    (Il existe un moyen pour un programme de dire au système d'exploitation de verrouiller une partie de son espace mémoire dans la RAM et de l'empêcher d'être échangé, mais ceux-ci nécessitent généralement des privilèges élevés. Dans tous les cas, le code que vous avez publié ne semble pas être en utilisant de telles méthodes.)

Fondamentalement, cette astuce ne semble utile que dans cette situation limite relativement étroite où l'utilisateur aurait suffisamment de RAM pour exécuter votre jeu (et tout autre logiciel fonctionnant activement en arrière-plan) sans échange, mais cette RAM est actuellement remplie de fichiers ouverts inactifs et inutilisés ou d'autres données qui peuvent et doivent être échangées sur le disque pendant que votre jeu est en cours d'exécution. Dans de telles situations, il peut être efficace pour rendre votre jeu plus fluide et plus réactif, en remplaçant les petites opérations de swap occasionnelles pendant le jeu par une seule lors du démarrage.

Ilmari Karonen
la source
1
De ma connaissance limitée de la gestion de la mémoire de bas niveau, je peux dire que la décision de permuter ou non une page spécifique est beaucoup plus complexe et prend en considération beaucoup plus de facteurs que la simple quantité de mémoire demandée et la quantité de mémoire disponible. . Trop simplifier cela et s'appuyer généralement sur des suppositions n'est pas très sage.
Panda Pyjama
3
Je pense qu'il est important de se rappeler que ce sont toutes des suppositions qui peuvent fonctionner, pas fonctionner, ou sembler fonctionner, mais pour des raisons totalement indépendantes. Pour autant que je sache, aucun des comportements mentionnés dans cette réponse n'est documenté, et il serait imprudent de s'y fier. Une mise à jour du système d'exploitation, un changement de paramètres, un changement de compilateur ou à peu près n'importe quoi peut faire en sorte que cela cesse de fonctionner, ou même avoir un impact négatif sur les performances.
Panda Pyjama
26

Je ne sais pas quel âge a cet article, mais je dirais qu'il est assez vieux. Dans Windows moderne (XP et plus récent, mais spécialement sur les versions 64 bits), je dirais que faire quelque chose comme ça aura peu ou pas d'impact négatif sur le démarrage de votre jeu.

Tout d'abord, n'oubliez pas que l'espace d'adressage est virtualisé pour chaque processus. En ce qui concerne votre processus, vous avez tout l'espace d'adressage pour vous et vous obtiendrez toujours une mémoire contiguë (virtuelle), que cette mémoire soit physiquement contiguë ou non.

En fait, le fait que la mémoire soit contiguë ou non à la mémoire physique n'est pas quelque chose que vous contrôlez. Le système d'exploitation choisira comment allouer les blocs physiques comme bon lui semble, et rien de ce que vous faites au niveau de l'application ne peut avoir d'effet sur les avantages (le cas échéant) d'avoir une mémoire physique contiguë.

Vous devez vous soucier de la fragmentation de la mémoire virtuelle si vous continuez à allouer et à libérer de la mémoire de différentes tailles pendant très longtemps. Ceci est bien sûr beaucoup moins pertinent avec un espace d'adressage 64 bits. Les jeux ne fonctionnent généralement pas pendant des mois, vous n'aurez donc probablement pas à vous en soucier, sauf si nous parlons de serveurs de jeux de longue durée.

Même si vous allouez beaucoup de mémoire de tailles très différentes dans une machine 32 bits, les allocations de mémoire modernes sont assez bonnes, il est donc peu probable que vous rencontriez des problèmes de fragmentation de la mémoire virtuelle à moins que vous ne les recherchiez activement

Pour être honnête avec vous, je ne sais pas de quoi parle cet écrivain. Même si l'espace d'adressage a été partagé et que vous avez obtenu un énorme bloc contigu dans la mémoire physique, en le libérant, vous dites que vous ne vous en souciez plus. Il y a d'autres programmes fonctionnant en arrière-plan qui font leurs propres allocations, donc même si votre énorme malloc a réussi, personne ne garantira que le prochain réussira également.

Je pense que c'est un cas de "cela a fonctionné pour une raison que je ne comprends pas vraiment, alors j'ai inventé quelque chose pour l'expliquer".

Pyjama Panda
la source
10
La RAM n'est pas comme un disque dur rotatif où avoir des blocs contigus est en quelque sorte "mieux" qu'improbable. Ce n'est pas vrai. l'accès RAM contigu est un ordre de grandeur plus rapide que l'accès aléatoire. Les puces RAM sont divisées en sections pour lesquelles il existe une certaine latence à commuter, et plus important encore, les échecs de cache L1 et L2 affecteront considérablement vos performances lorsque votre mémoire est dispersée.
CaptainCodeman
@CaptainCodeman: Je dois accepter que cela sort de mon domaine de connaissances, mais de toute façon, en tant que programmeur d'applications Windows, vous n'avez aucun contrôle sur le fait que votre mémoire soit physiquement contiguë ou non. Demander un énorme bloc de mémoire ne changera pas grand-chose.
Panda Pyjama
@CaptainCodeman a en fait un bloc virtuel contigu dans un bloc contigu mod 2 ^ N dans la RAM l'accélérera en raison de l'allocation de la ligne de cache
cliquetis monstre
8
@CaptainCodeman Oui, l'accès DRAM séquentiel est plus rapide, mais nous parlons du mappage virtuel-> physique qui se produit dans des pages de 4 Ko (ou plus), donc que ce mappage soit séquentiel ou non ne devrait pas avoir beaucoup d'impact sur la mémoire lire les vitesses. De plus, la prélecture du cache et d'autres choses du genre fonctionnent dans l'espace d'adressage virtuel, pas physique.
Nathan Reed
1
@NathanReed Très bien, et merci pour la clarification. J'exprimais simplement que la vitesse d'accès à la RAM n'est pas entièrement uniforme dans l'espace mémoire, comme cela a été suggéré.
CaptainCodeman