¿Asignar y liberar una gran cantidad de memoria al inicio "limpia la memoria"?

18

El libro Game Coding Complete, Cuarta edición , capítulo 5 ( Inicialización y apagado del juego ), sección Checking Memory contiene este interesante ejemplo de código:

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;
    }
}

Esto plantea algunas preguntas.

La primera parte solo le pregunta al sistema operativo (Windows) cuánta RAM física está disponible. La parte curiosa es la segunda, que asigna una gran cantidad de memoria y la libera de inmediato:

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

El autor continúa explicando:

... esta función asigna e inmediatamente libera un gran bloque de memoria. Esto tiene el efecto de hacer que Windows limpie cualquier basura que se haya acumulado en el administrador de memoria y verifique que pueda asignar un bloque contiguo tan grande como lo necesite. Si la llamada tiene éxito, esencialmente ha ejecutado el equivalente de una máquina Zamboni a través de la memoria de su sistema, preparándolo para que su juego llegue al hielo ...

Pero tengo mis reservas sobre eso.

"¿Limpiar la basura acumulada en el administrador de memoria?" De Verdad? Si el juego acaba de comenzar, ¿no debería haber basura?

"¿Asegurándote de que puedes asignar un bloque contiguo?" En el caso muy específico en el que va a administrar la memoria usted mismo, esto tendría algún sentido, pero aún así, si asigna mucha memoria directamente, es casi imposible que se ejecute cualquier otra aplicación. el sistema mientras el tuyo está encendido.

Además, ¿no es probable que esto obligue al sistema operativo a comprometer toda esa memoria y, como consecuencia, expulsar una gran cantidad de memoria en el espacio del disco de intercambio, ralentizando mucho el inicio de la aplicación?

¿Es realmente una buena práctica?

glampert
fuente
3
La mayoría de los sistemas operativos modernos no harán nada cuando una aplicación asigna una gran área de memoria, usan una asignación optimista y en realidad no hacen nada hasta que llenes la memoria, no puedo imaginar que esto haga nada más que ser un no-op potencialmente lento
Vality
¿Limpiar una larga franja de tierra en la jungla, tallar una radio, auriculares, etc. de la madera da como resultado el aterrizaje de aviones y la entrega de suministros?
Dan Neely
10
Juego de codificación completa contiene una gran cantidad de tonterías se combina con una gran cantidad de no-comprensión-C ++ y C-que-ve-un-poco-como-C ++ (también demostrado en esta muestra, comprobando el resultado de operator newa nullptr), si me lo permiten decir. Lo mejor que puedes hacer con ese libro es encender tu chimenea. Asignar y liberar un gran bloque de memoria, por supuesto , no "limpia" la memoria.
Damon
@Damon, no lo comprobé, pero sospecho que al menos han sobrecargado al newoperador global para que devuelva nulo en lugar de tirar bad_alloc. Si no lo hicieron, entonces sí, este código es aún más absurdo: P
glampert
1
@glampert: Incluso suponiendo que ese sea el caso, operator deletees necesario aceptarlo nullptry tratarlo como no operativo. Cualquier sobrecarga global que no haga eso está rota. Lo que significa que no tiene sentido de ninguna manera. Al igual que asumir que asignar un gran bloque de memoria y liberarlo "mágicamente" hará algo bueno. En el mejor de los casos, no hará ningún daño (lo más probable, ya que las páginas ni siquiera se tocan ... de lo contrario, puede cambiar algunas páginas de su conjunto de trabajo que deberá volver a cargar más adelante).
Damon

Respuestas:

12

Una cosa que podría hacer la asignación previa de una gran cantidad de memoria , si su computadora tenía poca RAM, podría ser forzar al sistema operativo a liberar algo de espacio adicional intercambiando partes del espacio de memoria de otros programas en el disco.

Dado que tal intercambio es generalmente una operación muy lenta que congela su programa mientras está sucediendo, podría haber alguna ventaja para garantizar que, si va a suceder de todos modos, sucederá antes de que comience su juego en lugar de estar en el medio del juego.

Dicho esto, forzar un intercambio a disco como este no es exactamente 100% confiable:

  • En muchas implementaciones de memoria virtual, la simple asignación de memoria en realidad no desencadena ningún intercambio; más bien, solo sucede cuando accede por primera vez a cada página de la memoria que asignó. (Esto es cierto en las versiones modernas de Linux, con el exceso de memoria habilitado, como lo es por defecto; no estoy seguro de cómo las manejan las diferentes versiones de Windows).

    Por lo tanto, si realmente desea que este código "limpie la memoria", probablemente debería agregar una memset()llamada o equivalente para escribir datos en cada parte de la matriz (o al menos en los primeros physicalRAMNeededbytes) antes de liberarla.

  • Además, obligar al sistema operativo a intercambiar otros programas de la RAM de esta manera no significa que se mantendrán al margen . Windows es un sistema operativo multitarea, y tan pronto como uno de esos programas intercambiados quiera volver a ejecutarse y usar sus datos intercambiados, volverá a conectarse, lo que podría congelar su juego nuevamente.

    Por lo tanto, este truco generalmente solo es útil si la memoria RAM está siendo absorbida por grandes cantidades de datos a los que no se accede activamente (como documentos grandes que quedan abiertos en segundo plano en algún software de edición). Los navegadores web tienden a ser particularmente problemáticos a este respecto, ya que, incluso si no está utilizando una página web abierta en alguna pestaña de fondo, la página aún puede tener secuencias de comandos ejecutándose que obligan a mantenerla en la RAM.

    (No son maneras para que un programa de realidad decirle al sistema operativo para bloquear una parte de su espacio de memoria en la memoria RAM y evitar que ser llevados a swap, pero éstos normalmente requieren privilegios elevados. En cualquier caso, el código que envió no parece estar usando cualquiera de estos métodos.)

Básicamente, este truco solo parece útil en esa situación límite relativamente estrecha en la que el usuario tendría suficiente RAM para ejecutar su juego (y cualquier otro software que se ejecute activamente en segundo plano) sin intercambiar, pero esa RAM está actualmente llena de archivos abiertos inactivos y no utilizados u otros datos que pueden y deben intercambiarse en el disco mientras se ejecuta el juego. En tales situaciones, puede ser efectivo para hacer que su juego parezca más fluido y receptivo, al reemplazar ocasionalmente pequeñas operaciones de intercambio durante el juego con una sola durante el inicio.

Ilmari Karonen
fuente
1
Desde mi conocimiento limitado de la gestión de memoria de bajo nivel, puedo decir que la decisión de intercambiar o no una página específica es mucho más compleja y tiene en cuenta muchos más factores que la simple cantidad de memoria solicitada y la cantidad de memoria disponible . Simplificar demasiado esto y, en general, confiar en suposiciones no es muy sabio.
Panda Pyjama
3
Creo que es importante recordar que todas estas son suposiciones que pueden funcionar, no funcionar o parecer funcionar, pero por razones completamente ajenas. Hasta donde yo sé, ninguno de los comportamientos señalados en esta respuesta están documentados, y no sería prudente confiar en ellos. Una actualización del sistema operativo, cambio de configuración, cambio de compilador o casi cualquier cosa puede hacer que esto deje de funcionar o incluso tener un impacto negativo en el rendimiento.
Panda Pyjama
26

No sé cuántos años tiene ese escrito, pero diría que es bastante viejo. En Windows moderno (XP y más reciente, pero especialmente en versiones de 64 bits), diría que hacer algo así tendrá poco o un impacto negativo en el inicio de su juego.

En primer lugar, recuerde que el espacio de direcciones está virtualizado para cada proceso. En lo que respecta a su proceso, tiene todo el espacio de direcciones para usted y siempre obtendrá memoria contigua (virtual), independientemente de si esa memoria es físicamente contigua o no.

De hecho, si la memoria es contigua o no en la memoria física no es algo sobre lo que tenga control. El sistema operativo elegirá cómo asignar los bloques físicos como mejor le parezca, y nada de lo que haga a nivel de aplicación puede afectar los beneficios (si los hay) de tener memoria física contigua.

Debe preocuparse por la fragmentación de la memoria virtual si sigue asignando y liberando memoria de diferentes tamaños durante un período de tiempo muy largo. Por supuesto, esto es mucho menos relevante con un espacio de direcciones de 64 bits. Los juegos generalmente no se ejecutan durante meses, por lo que es probable que no tenga que preocuparse por esto, a menos que estemos hablando de servidores de juegos de larga duración.

Incluso si asigna mucha memoria de tamaños muy diferentes en una máquina de 32 bits, las asignaciones de memoria modernas son bastante buenas, por lo que es poco probable que tenga problemas de fragmentación de memoria virtual a menos que los esté buscando activamente

Para ser honesto contigo, no sé de qué está hablando ese escritor. Incluso si el espacio de direcciones fue compartido, y usted obtuvo un gran bloque contiguo en la memoria física, al liberarlo, está diciendo que ya no le importa. Hay otros programas que se ejecutan en segundo plano haciendo sus propias asignaciones, por lo que incluso si su gran malloc tuvo éxito, nadie garantizará que el próximo tenga éxito también.

Creo que es un caso de "funcionó por alguna razón que realmente no entiendo, así que hice algo para explicarlo".

Pijama Panda
fuente
10
RAM no es como un disco duro giratorio donde tener bloques contiguos es de alguna manera "mejor" que no. Esto no es verdad. El acceso contiguo a la RAM es un orden de magnitud más rápido que el acceso aleatorio. Los chips de RAM se dividen en secciones para las cuales hay una cierta cantidad de latencia para cambiar, y lo más importante, las fallas de caché L1 y L2 afectarán drásticamente su rendimiento cuando su memoria esté dispersa.
CaptainCodeman
@CaptainCodeman: Tengo que aceptar que se sale de mi campo de conocimiento, pero de cualquier manera, como programador de aplicaciones de Windows no tienes control sobre si tu memoria es físicamente contigua o no. Pedir un gran bloque de memoria no cambiará mucho.
Panda Pyjama
@CaptainCodeman en realidad tiene un bloque virtual contiguo en modo contiguo 2 ^ N el bloque en RAM lo acelerará debido a la asignación de línea de caché
monstruo de trinquete
8
@CaptainCodeman Sí, el acceso secuencial a la DRAM es más rápido, pero estamos hablando del mapeo virtual-> físico que ocurre en páginas de 4 KB (o más), por lo que si el mapeo es secuencial o no no debería tener mucho impacto en la memoria velocidades de lectura Además, la captación previa de caché y cosas así funcionan en el espacio de direcciones virtuales, no en el físico.
Nathan Reed
1
@NathanReed Bastante justo, y gracias por la aclaración. Estaba expresando que la velocidad de acceso a la RAM no es completamente uniforme en todo el espacio de memoria, como se sugirió.
CaptainCodeman