Uso de memoria en fortran cuando se usa una matriz de tipo derivado con puntero

13

En este programa de muestra estoy haciendo lo mismo (al menos eso creo) de dos maneras diferentes. Estoy ejecutando esto en mi PC con Linux y monitoreando el uso de memoria con top. Usando gfortran encuentro que en la primera forma (entre "1" y "2") la memoria utilizada es de 8.2GB, mientras que en la segunda forma (entre "2" y "3") el uso de la memoria es de 3.0GB. Con el compilador de Intel, la diferencia es aún mayor: 10GB frente a 3GB. Esto parece una penalización excesiva por usar punteros. ¿Por qué pasó esto?

program test
implicit none

  type nodesType
    integer:: nnodes
    integer,dimension(:),pointer:: nodes 
  end type nodesType

  type nodesType2
    integer:: nnodes
    integer,dimension(4):: nodes 
  end type nodesType2

  type(nodesType),dimension(:),allocatable:: FaceList
  type(nodesType2),dimension(:),allocatable:: FaceList2

  integer:: n,i

  n = 100000000

  print *, '1'
  read(*,*)
  allocate(FaceList(n))
  do i=1,n
    FaceList(i)%nnodes = 4
    allocate(FaceList(i)%nodes(4))
    FaceList(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '2'
  read(*,*)

  do i=1,n
    deallocate(FaceList(i)%nodes)
  end do
  deallocate(FaceList)

  allocate(FaceList2(n))
  do i=1,n
    FaceList2(i)%nnodes = 4
    FaceList2(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '3'
  read(*,*)

end program test

El fondo es el refinamiento de la red local. Elegí la lista vinculada para agregar y eliminar caras fácilmente. El número de nodos es 4 de forma predeterminada, pero puede aumentar según los refinamientos locales.

Chris
fuente
1
La "primera forma" debe evitarse tanto como sea posible, ya que es propenso a fugas (las matrices deben desasignarse explícitamente, como lo hizo) además de la diferencia en el rendimiento que ve. La única razón para usarlo sería la estricta adherencia a Fortran 95. Las asignaciones en tipos derivados se agregaron en TR 15581, pero todos los compiladores de Fortran (incluso los que no tienen características de 2003) los han admitido, es decir, F95 + TR15581 + TR15580 desde siempre. .
stali
1
La razón para hacerlo es que algunas caras pueden tener más de 4 nodos.
Chris
Entonces ciertamente tiene sentido. Supuse que 4 era una constante.
stali

Respuestas:

6

Realmente no sé cómo funcionan los compiladores fortran, pero según las características del lenguaje, puedo adivinar.

Las matrices dinámicas en fortran vienen con metadatos para trabajar con funciones intrínsecas como forma, tamaño, lbound, ubound y asignadas o asociadas (asignables vs punteros). Para matrices grandes, el tamaño de los metadatos es insignificante, pero para matrices pequeñas, como en su caso, puede sumar. En su caso, las matrices dinámicas de tamaño 4 probablemente tengan más metadatos que datos reales, lo que está llevando a su globo de uso de memoria.

Recomiendo encarecidamente contra la memoria dinámica en la parte inferior de sus estructuras. Si está escribiendo un código que trata con sistemas físicos en cierto número de dimensiones, puede configurarlo como una macro y volver a compilar. Si se trata de gráficos, puede asignar estáticamente un límite superior en el número de bordes o me gusta. Si se trata de un sistema que realmente necesita un control dinámico de memoria de grano fino, entonces probablemente sea mejor cambiar a C.

Max Hutchinson
fuente
Sí, pero ¿no es cierto el argumento de metadatos para ambos casos?
stali
@stali no, tenga en cuenta que el segundo caso requiere un puntero, a diferencia de los npunteros necesarios para el primer método.
Aron Ahmadia 01 de
He agregado información de fondo. Su sugerencia de asignar estáticamente un límite superior ya es una buena mejora. El límite superior es 8, pero la mayoría tendrá 4, solo un pequeño porcentaje tendrá 5,6,7 u 8. Por lo tanto, la memoria aún se desperdicia ...
Chris
@chris: ¿Puedes hacer dos listas, una con 4 y otra con 8 nodos?
Pedro
Probablemente. Parece ser un buen compromiso.
Chris
5

Como ha señalado maxhutch , el problema es probablemente la gran cantidad de asignaciones de memoria separadas. Sin embargo, además de los metadatos, probablemente existan los datos y la alineación adicionales que necesita el administrador de memoria, es decir, probablemente esté redondeando cada asignación a un múltiplo de 64 bytes o más.

Para evitar asignar un pequeño fragmento para cada nodo, puede intentar asignar a cada nodo una parte de una matriz preasignada:

integer :: finger
indeger, dimension(8*n) :: theNodes

finger = 1
do i=1,n
    FaceList(i)%nodes => theNodes(finger:finger+FaceList(i)%nnodes-1)
    finger = finger + FaceList(i)%nnodes
end do

Mi Fortran está un poco oxidado, pero lo anterior debería funcionar, si no en principio.

Todavía tendría los gastos generales de lo que su compilador Fortran cree que necesita almacenar para un tipo de PUNTERO, pero no tendrá los gastos generales del administrador de memoria.

Pedro
fuente
esto ayuda pero solo un poco. El problema es que no es un puntero único sino una matriz dinámica de punteros: FaceList (i)% nodos (1: FaceList (i)% nnodes) => theNodes (finger: finger + FaceList (i)% nnodes-1). También implica una estimación precisa del tamaño de la matriz preasignada.
Chris
@chris: No estoy seguro de entender por completo ... ¿Qué quieres decir con "matriz dinámica de punteros"? El campo nodesType%nodeses un puntero a una matriz dinámica.
Pedro
0

Oh. Este es el mismo problema que sufrí. Esta pregunta es muy antigua, pero sugiero un estilo de código un poco diferente. Mi problema era la matriz de sentencias asignables en el tipo de datos derivados, como sigue el código.

type :: node
  real*4,dimension(:),allocatable :: var4
  real*8,dimension(:),allocatable :: var8
end type node

type(node),dimension(:),allocatable :: nds

imax = 5000
allocate(nds(imax))

De alguna prueba, confirme que si utilicé la instrucción asignable o la instrucción de puntero en el tipo derivado como código siguiente sobre cuatro casos, la pérdida de memoria ocurre muy grande. En mi caso, rojo el archivo de 520MB de tamaño. Pero el uso de memoria fue de 4 GB en modo de lanzamiento en Intel Fortran. ¡Eso es 8 veces más grande!

!(case 1) real*4,dimension(:),allocatable :: var4
!(case 2) real*4,dimension(:),pointer :: var4
!(case 3) real*4,allocatable :: var4(:,:)

!(case 4) 
type :: node(k)
  integer,len :: k = 4
  real*4 :: var4(k)
end type node

La pérdida de memoria no ocurre cuando uso una instrucción asignable o de puntero sin tipo derivado. En mi opinión, si declaro la variable de tipo asignable o de puntero en tipo derivado y asigno grande la variable de tipo derivado no variable variable en el tipo derivado, se produce una fuga de memoria. Para resolver este problema, cambié mi código que no incluye el tipo derivado como código siguiente.

real*4,dimension(:,:),allocatable :: var4 
! array index = (Num. of Nodes, Num. of Variables)

o que tal este estilo?

integer,dimension(:),allocatable :: NumNodes ! (:)=Num. of Cell or Face or etc.
integer,dimension(:),allocatable :: Node     ! (:)=(Sum(NumNodes))

La variable NumNodes significa el número de nodos en cada cara y la variable Node es el número de nodo que coincide con la variable NumNodes. Tal vez no se haya producido una pérdida de memoria en este estilo de código, creo.

G.Ku
fuente