(¿Cómo) tiene en cuenta la fragmentación de la memoria?


8

Utilizo un ejemplo de la teoría de elementos finitos, pero cualquiera que mantenga una gran estructura de datos y la extienda sucesivamente encontrará algo similar.

Suponga que tengo una malla no estructurada de puntos y triángulos, donde los puntos están dados por coordenadas (digamos e ) y los triángulos consisten en tres índices de puntos (digamos , y ).Xyyojk

Como es común en FEM, la malla será sucesivamente refinada. Si recurrimos al refinamiento regular global, el número de triángulos crecerá en un factor con cada iteración del refinamiento. Dependiendo de cómo se haga esto, el diseño de la memoria se desarrollará de manera diferente.4 4

Digamos que la malla oculta las celdas de memoria 1 a 300, cualquier cosa más allá de eso está libre.

Ejemplo 1:

Asignamos el espacio para la nueva malla, celdas 301 a 1501, la llenamos con los datos de la malla refinada y olvidamos la anterior. La siguiente malla refinada se colocará en las celdas 1501 a 6300, la siguiente en 6301 a 21500, y así sucesivamente. La ubicación de la malla actual se moverá en la memoria "hacia la derecha", mientras que no se utilizará un parche enorme. Podemos quedarnos sin memoria prematuramente.

Se podría observar en el ejemplo anterior, que esto solo nos dificultará un paso más, porque incluso sin esa fragmentación nos quedaríamos sin memoria total un refinamiento más tarde. A medida que también se tiene en cuenta la matriz de vértices, el problema puede ser más grave.

¿Cómo se puede evitar esto?

Ejemplo 2

Vuelva a asignar la matriz de triángulos a las celdas 1..1200. Cree la nueva malla en las celdas 1201 a 2400. Copie el contenido de esa copia de trabajo en las celdas 1..1200 y olvide la copia de trabajo. Repite de manera similar.

Ok, todavía nos quedamos sin memoria prematuramente, porque necesitamos una copia que funcione. Qué tal esto:

Ejemplo 3

Vuelva a asignar la matriz de triángulos a las celdas 1..1500. Copie la malla antigua a 1201 .. 1500. Cree una nueva malla en las celdas 1..1200. Luego olvida la copia de la malla vieja.

El caso aquí es artificial, porque uno no usaría el refinamiento de malla global en estas escalas. Si el crecimiento es mucho menor, la realineación de la memoria es posible para evitar la fragmentación. Sin embargo,

Preguntas:

  1. ¿La fragmentación de la memoria se vuelve crítica en la informática científica práctica / informática de alto rendimiento?

  2. Si es así, ¿cómo lo evitas? Tal vez mi modelo de máquina está incluso equivocado, y el sistema operativo, por magia pesada, realinea la memoria tácitamente o gestiona bloques fragmentados en el montón.

  3. Más específicamente, ¿cómo afecta a la gestión de la red?

Respuestas:


11

No estoy de acuerdo con Matt sobre el uso de memoria total para la malla y sobre la indirección adicional. Para métodos explícitos, es común que un número muy pequeño de vectores (por ejemplo, 2) represente todos los estados de simulación. Para un problema escalar, solo definir las coordenadas de la malla puede ser más que esto, y si la conectividad es explícita, es sustancialmente más. Las simulaciones de alta resolución con métodos explícitos con frecuencia necesitan una gran cantidad de pasos de tiempo, por lo que es común usar subdominios razonablemente pequeños solo para poder completar un modelo ejecutado en una cantidad razonable de tiempo de pared. Por lo tanto, no es tan común quedarse sin memoria debido al costo de almacenar la malla.

Un problema mucho más fundamental es el requisito de ancho de banda de memoria para cada paso de un algoritmo (por ejemplo, evaluar un residual o tomar un paso de tiempo). Para los métodos no estructurados, esto implica cierta información basada en mallas, pero generalmente no requiere acceder a todo el almacenamiento de mallas. Tenga en cuenta que incluso si la memoria del solucionador domina la memoria de malla (como es común para muchos métodos implícitos), la simulación todavía depende con frecuencia de los requisitos de ancho de banda de un recorrido de malla. Hay dos factores:

¿Qué datos se necesitan durante el recorrido de malla de rutina?

En los métodos de elementos finitos, se necesita un mapa de elemento a global. Para un orden superior al primer orden, esto a menudo se implementa asociando grados de libertad con entidades intermedias como caras y aristas, pero una vez que se construye el mapa de elemento a global, las entidades intermedias no son necesarias. En particular, la conectividad de las entidades intermedias nunca es utilizada directamente por una simulación. Es posible que esa información no se toque durante muchas iteraciones de un solucionador o integrador de tiempo implícito, pero aún así se requiere durante la adaptabilidad o para configurar un nuevo espacio de funciones. Es común configurar el mapa de elemento a global en una matriz simple y no tocar la estructura de datos de "malla" durante este período, en cuyo caso el formato de almacenamiento y la localidad de datos de la malla en sí es menos importante.

Los métodos de volumen finito requieren los volúmenes de celda, conectividad cara a celda, área de cara y normales de cara. Tenga en cuenta que las coordenadas de vértice o la conectividad no necesitan estar disponibles. En ejemplos extremos (por ejemplo, FUN3D), toda la otra información se descarta durante el preprocesamiento fuera de línea (generación de malla) y solo esta representación reducida está disponible para la simulación. Esto es muy eficiente, pero impide el movimiento de las mallas y el refinamiento adaptativo.

¿Cómo se presentan esos datos en la memoria?

Para muchas simulaciones, con órdenes modestos de precisión y complejidad de la física, el rendimiento está limitado por el ancho de banda de la memoria. Las arquitecturas modernas de CPU de IBM, Intel, AMD y NVidia admiten una intensidad aritmética entre 4 y 8 flops / byte. Con unidades de punto flotante tan eficientes, deberíamos intentar optimizar nuestros algoritmos para el ancho de banda de la memoria. Esto podría implicar cambios algorítmicos algo profundos, como favorecer los métodos de alto orden no ensamblados (compare las líneas "tensoras" ensambladas y (no ensambladas) en la segunda figura), pero podemos comenzar intentando utilizar completamente el ancho de banda proporcionado por el hardware. Esto a menudo implica el ordenamiento informático (de vértices, caras, celdas, etc.) de modo que el caché se reutilice lo mejor posible. También implica explotar el bloqueo y ordenar incógnitas para tener metadatos mínimos y activar la cantidad correcta de flujos de datos. (Por lo general, la simulación 3D no estructurada termina con "demasiadas" transmisiones, pero algunos equipos modernos como POWER7 necesitan muchas transmisiones para saturar el ancho de banda, por lo que ocasionalmente tiene sentido organizar intencionalmente los datos para activar más transmisiones). El trabajo PETSc-FUN3D proporciona un clásico , pero sigue siendo una discusión muy relevante de estas optimizaciones de rendimiento para CFD implícitos no estructurados.

Sugerencias para su problema.

  1. No tengas miedo malloc(). No es necesario empaquetar el refinamiento actual de la malla en la misma matriz que la parte inactiva más gruesa de la malla. Cada vez que ya no necesite una parte vieja de la malla, simplemente free().

  2. Después del refinamiento, calcule un buen orden para ese nivel de refinamiento ( Reverse Cuthill-McKee es popular, pero también se pueden usar más pedidos específicos de caché). Si su desequilibrio de carga es razonable (menos del 10%, por ejemplo), puede calcular este nuevo pedido localmente (sin particiones y redistribuciones paralelas). El costo probablemente será similar a un solo recorrido de malla "física", pero puede acelerar esos recorridos en un factor de 2 o más. Este paso generalmente dará sus frutos siempre que no se produzca adaptabilidad de malla en cada"física" de malla transversal. Si se requiere una adaptabilidad tan frecuente para su problema, es probable que esté haciendo pequeños cambios y que aún así deba reordenar ocasionalmente. Todavía evitaría las agrupaciones de memoria de grano fino porque hace que la reordenación sea más difícil, pero puede usar fragmentos razonablemente grandes para equilibrar el uso máximo de memoria, el costo de pedidos y el costo de las actualizaciones incrementales.

  3. Dependiendo de la discretización, considere extraer una representación reducida con bajos requisitos de ancho de banda de memoria para los recorridos "físicos". La indirección innecesaria es mala porque aumenta los requisitos de ancho de banda y, si el objetivo de la indirección es irregular, provoca una reutilización deficiente de la caché e inhibe la captación previa.


Este problema sobre la conectividad intermedia que ocupa el ancho de banda es trivial para eliminar en su caso, y se hace con PETSc DMComplex. Se divorcia del almacenamiento de la topología de malla de los datos de campo. Almacenar elemento a global es incorrecto en todos los casos, creo. La topología tiene la misma información en un espacio más pequeño y es mucho más rica. Usted compone un recorrido, como un cierre, con compensaciones en el almacenamiento.
Matt Knepley

5

En deal.II, refinamos la malla tirando las celdas viejas y reemplazándolas por otras nuevas. Pero también se colocan otros nuevos en los agujeros de memoria que dejan las celdas eliminadas. Todos los bucles sobre todas las celdas se realizan en el orden en que se encuentran las celdas en la memoria para mantener altos los éxitos de caché.

La pregunta más importante es cómo almacena los datos que definen las celdas. Por supuesto, puede hacer struct Simplex {Vértices vértices [4]; int material_id; int subdomain_id; bool usado; void * user_data; };

clase Triangulación {Simplex * celdas; }; pero esto no es eficiente en caché porque la mayoría de los bucles en todas las celdas solo tocarán un subconjunto de los datos que almacena en su estructura de datos Simplex y, por lo tanto, solo se usará una fracción de los datos que aterrizan en la caché. Una mejor estrategia es hacer algo como esto: clase Triangulación {Vértices * vértices; int * material_ids; int * subdomain_ids; bool * used_flags; void * * user_data; }; Debido a que en los bucles sobre todas las celdas, es probable que las iteraciones posteriores accedan al mismo subconjunto de datos que define las celdas, los cachés de lectura anticipada solo precargarán los datos que realmente va a usar y, en consecuencia, conducirán a altas tasas de aciertos de caché.


4

1) No. La memoria del solucionador supera con creces la memoria de la malla. Incluso si ejecuta el solucionador explícito más ligero, la malla es como máximo el 25% de la memoria de la simulación, y es mucho más probable que sea <10%.

2) Desglose su asignación y use la agrupación de memoria. No necesita asignar un fragmento contiguo para toda la malla, ya que generalmente solo necesita iterar sobre piezas locales. La introducción de un nivel de indirección no afecta el rendimiento de manera significativa.


¿Alguna referencia para este reclamo? Un método simple sin matriz debería tener una huella de solucionador muy ligera.
aterrel

2
Claro, si lo hace sin matriz, puede ser un problema diferente. Pero, aparte de eso, es fácil ver que el comentario de Matt es correcto: por cada grado de libertad en la malla, la matriz tiene que almacenar tantos dobles como se acopla este DoF, que en todos los casos 3D no triviales llega a cientos (cuente esto una vez para un elemento Q2-Q1 / Taylor-Hood Stokes en 3d). Verá fácilmente que esto requiere mucha más memoria que los datos que definen la malla.
Wolfgang Bangerth

0

Si se está quedando sin memoria, simplemente ejecute en más nodos para tener más memoria. Está desperdiciando un recurso muy valioso (el cerebro humano) para resolver un problema que tiene una solución muy fácil.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.