Uso de memória no fortran ao usar uma matriz do tipo derivado com ponteiro

13

Neste programa de exemplo, estou fazendo a mesma coisa (pelo menos acho) de duas maneiras diferentes. Estou executando isso no meu pc Linux e monitorando o uso de memória com o top. Usando o gfortran, acho que na primeira maneira (entre "1" e "2") a memória usada é de 8,2 GB, enquanto na segunda maneira (entre "2" e "3") o uso da memória é de 3,0 GB. Com o compilador Intel, a diferença é ainda maior: 10 GB versus 3 GB. Isso parece uma penalidade excessiva pelo uso de ponteiros. Por que isso acontece?

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

O plano de fundo é o refinamento da grade local. Eu escolhi a lista vinculada para adicionar e remover faces facilmente. O número de nós é 4 por padrão, mas pode se tornar maior dependendo dos refinamentos locais.

chris
fonte
1
A "primeira maneira" deve ser evitada o máximo possível, pois é propensa a vazamentos (as matrizes devem ser desalocadas explicitamente, como você fez), além da diferença de desempenho que você vê. O único motivo para usá-lo seria a aderência estrita ao Fortran 95. Os tipos alocáveis ​​nos tipos derivados foram adicionados no TR 15581, mas todos os compiladores Fortran (mesmo aqueles que não possuem recursos de 2003) os suportam, ou seja, F95 + TR15581 + TR15580 desde sempre .
Stali #
1
O motivo para isso é que algumas faces podem ter mais de 4 nós.
chris
Então certamente faz sentido. Eu assumi que 4 era uma constante.
Stali #

Respostas:

6

Na verdade, não sei como os compiladores fortran funcionam, mas com base nos recursos da linguagem, posso adivinhar.

As matrizes dinâmicas no fortran vêm com metadados para trabalhar com funções intrínsecas como forma, tamanho, ligação, ligação e alocação ou associação (alocáveis ​​versus ponteiros). Para matrizes grandes, o tamanho dos metadados é desprezível, mas para matrizes pequenas, como no seu caso, elas podem aumentar. No seu caso, as matrizes dinâmicas tamanho 4 provavelmente têm mais metadados do que dados reais, o que leva ao seu balão de uso de memória.

Eu recomendo fortemente contra a memória dinâmica na parte inferior de suas estruturas. Se você estiver escrevendo um código que lida com sistemas físicos em algum número de dimensões, pode defini-lo como uma macro e recompilar. Se você lida com gráficos, pode alocar estaticamente um limite superior no número de arestas ou nos gostos. Se você estiver lidando com um sistema que realmente precisa de controle de memória dinâmica de baixa granularidade, provavelmente é melhor mudar para C.

Max Hutchinson
fonte
Sim, mas o argumento dos metadados não se aplica aos dois casos?
Stali #
@stali não, observe que o segundo caso requer um ponteiro, em oposição aos nponteiros necessários ao primeiro método.
Aron Ahmadia
Adicionei algumas informações básicas. Sua sugestão de alocar estaticamente um limite superior já é uma boa melhoria. O limite superior é de 8, mas a maioria terá 4, apenas uma pequena percentagem terá 5,6,7 ou 8. Assim, a memória ainda é desperdiçado ...
chris
@ Chris: Você pode fazer duas listas, uma com 4 e outra com 8 nós?
Pedro
Provavelmente. Parece ser um bom compromisso.
22412 chris #
5

Como o maxhutch apontou, o problema provavelmente é o grande número de alocações de memória separadas. No entanto, além dos metadados, provavelmente existem dados e alinhamentos adicionais que o gerenciador de memória precisa, ou seja, provavelmente está arredondando cada alocação para vários múltiplos de 64 bytes ou mais.

Para evitar a alocação de um pequeno pedaço para cada nó, tente alocar a cada nó uma parte de uma matriz pré-alocada:

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

Meu Fortran está um pouco enferrujado, mas o acima deve funcionar, se não em princípio.

Você ainda teria os custos indiretos do que o compilador Fortran acha que precisa armazenar para um tipo POINTER, mas não terá os custos indiretos do gerenciador de memória.

Pedro
fonte
isso ajuda, mas apenas um pouco. O problema é que não é um ponteiro único, mas uma matriz dinâmica de ponteiros: FaceList (i)% nós (1: FaceList (i)% nnodes) => theNodes (dedo: dedo + FaceList (i)% nnodes-1). Isso também implica uma estimativa precisa do tamanho da matriz pré-alocada.
22412 chris #
@chris: Não sei se entendi completamente ... O que você quer dizer com "matriz dinâmica de ponteiros"? O campo nodesType%nodesé um ponteiro para uma matriz dinâmica.
Pedro
0

Oh Este é o mesmo problema que sofri. Essa pergunta é muito antiga, mas sugiro um estilo de código um pouco diferente. Meu problema foi a matriz de instruções alocáveis ​​no tipo de dados derivado, como segue o 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 algum teste, confirmei que se eu usasse a instrução alocável ou a indicação do ponteiro no tipo derivado como código a seguir em quatro casos, o vazamento de memória ocorreria muito grande. No meu caso, eu vermelho o arquivo do tamanho de 520MB. Mas o uso da memória foi de 4 GB no modo de liberação no intel fortran complier. Isso é 8 vezes maior!

!(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

O vazamento de memória não ocorre quando eu uso a instrução alocável ou ponteiro sem o tipo derivado. Na minha opinião, se eu declarar a variável do tipo alocável ou ponteiro no tipo derivado e grande alocar a variável do tipo derivado não variável alocável no tipo derivado, ocorrerá um vazamento de memória. Para resolver esse problema, alterei meu código que não inclui o tipo derivado como código a seguir.

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

ou que tal esse estilo?

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

A variável NumNodes significa o número de nós em cada face e a variável Node é o número do nó correspondente à variável NumNodes. Talvez o vazamento de memória não ocorra nesse estilo de código, eu acho.

G.Ku
fonte