포인터가있는 파생 유형의 배열을 사용할 때 포트란의 메모리 사용량


13

이 샘플 프로그램에서 저는 두 가지 다른 방식으로 같은 일을하고 있습니다 (적어도 그렇게 생각합니다). Linux PC에서 이것을 실행하고 top으로 메모리 사용량을 모니터링합니다. gfortran을 사용하여 첫 번째 방법 ( "1"과 "2"사이)에서 사용 된 메모리는 8.2GB이고 두 번째 방법 ( "2"와 "3"사이)에서 메모리 사용량은 3.0GB입니다. 인텔 컴파일러의 차이는 10GB와 3GB보다 훨씬 큽니다. 이것은 포인터 사용에 대한 과도한 처벌로 보입니다. 왜 이런 일이 발생합니까?

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

배경은 로컬 그리드 개선입니다. 얼굴을 쉽게 추가하고 제거하기 위해 연결된 목록을 선택했습니다. 노드 수는 기본적으로 4이지만 로컬 세분화에 따라 더 높아질 수 있습니다.


1
"첫 번째 방법"은 성능 차이 외에 누출이 발생하기 쉬우므로 (어레이는 명시 적으로 할당 해제해야 함) 가능한 한 피해야합니다. 이를 사용하는 유일한 이유는 Fortran 95를 엄격히 준수하기위한 것입니다. 파생 유형의 Allocatable은 TR 15581에 추가되었지만 모든 Fortran 컴파일러 (2003 기능이없는 컴파일러도 포함)가이를 지원합니다. 예를 들어 F95 + TR15581 + TR15580 .
stali

1
그렇게하는 이유는 일부면에 노드가 4 개 이상있을 수 있기 때문입니다.
chris

그렇다면 분명히 의미가 있습니다. 나는 4가 상수라고 가정했다.
stali

답변:


6

실제로 포트란 컴파일러의 작동 방식을 모르지만 언어 기능에 따라 추측 할 수 있습니다.

포트란의 동적 배열에는 모양, 크기, lbound, ubound 및 할당 또는 관련 (할당 가능한 대 포인터)과 같은 내장 함수와 함께 작동하는 메타 데이터가 제공됩니다. 큰 배열의 경우 메타 데이터의 크기는 무시할 만하지 만 작은 배열의 경우에는 추가 할 수 있습니다. 귀하의 경우, 크기 4 동적 배열은 실제 데이터보다 더 많은 메타 데이터를 가지고 있기 때문에 메모리 사용량 풍선으로 이어집니다.

구조체의 맨 아래에 동적 메모리를 사용하지 않는 것이 좋습니다. 특정 차원의 물리적 시스템을 다루는 코드를 작성하는 경우 매크로로 설정하고 다시 컴파일 할 수 있습니다. 그래프를 다루는 경우 가장자리 수 등을 정적으로 할당 할 수 있습니다. 실제로 미세한 동적 메모리 제어가 필요한 시스템을 다루는 경우 C로 전환하는 것이 가장 좋습니다.


예, 그러나 메타 데이터 인수가 두 경우 모두에 해당되지 않습니까?
stali

@stali no, n첫 번째 방법에 필요한 포인터 와 달리 두 번째 경우에는 하나의 포인터가 필요합니다.
Aron Ahmadia

배경 정보를 추가했습니다. 상한을 정적으로 할당하라는 제안은 이미 좋은 개선입니다. 상한은 8이지만 대다수는 4, 소수는 5,6,7 또는 8입니다. 따라서 메모리는 여전히 낭비됩니다 ...
chris

@ chris : 두 개의 목록을 만들 수 있습니까? 하나는 4 개, 하나는 8 개의 노드가 있습니까?
페드로

아마. 좋은 타협 인 것 같습니다.
chris

5

으로 maxhutch이 뾰족한 아웃이 문제는 별도의 메모리 할당의 깎아 지른듯한 숫자는 아마입니다. 그러나 메타 데이터 외에도 메모리 관리자에 필요한 추가 데이터 및 정렬이있을 수 있습니다. 즉, 각 할당을 64 바이트 이상의 배수로 반올림 할 수 있습니다.

각 노드에 작은 청크를 할당하지 않으려면 각 노드 에 미리 할당 된 배열 의 일부 를 할당하십시오.

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

내 포트란은 약간 녹슬지 만, 원칙적으로 그렇지 않으면 위의 방법으로 작동합니다.

여전히 포트란 컴파일러가 POINTER 유형을 위해 저장해야한다고 생각하는 것에 대한 오버 헤드는 있지만 메모리 관리자 오버 헤드는 없습니다.


이것은 조금만 도움이됩니다. 문제는 단일 포인터가 아니라 동적 포인터의 배열입니다. FaceList (i) % nodes (1 : FaceList (i) % nnodes) => theNodes (finger : finger + FaceList (i) % nnodes-1). 또한 사전 할당 된 어레이의 크기에 대한 정확한 추정치를 의미합니다.
chris

@ chris : 완전히 이해하고 있는지 잘 모르겠습니다 ... "동적 포인터 배열"이란 무엇입니까? 이 필드 nodesType%nodes는 동적 배열에 대한 포인터입니다.
페드로

0

오. 이것은 내가 겪었던 것과 같은 문제입니다. 이 질문은 매우 오래되었지만 조금 다른 코드 스타일을 제안합니다. 내 문제는 다음 코드와 같이 파생 데이터 형식의 할당 가능한 명령문 배열이었습니다.

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))

일부 테스트에서 파생 된 유형의 할당 가능한 명령문 또는 포인터 명령문을 네 가지 경우에 대한 다음 코드로 사용하면 메모리 누수가 매우 큰 것으로 확인되었습니다. 필자의 경우 520MB 크기의 파일을 빨간색으로 표시했습니다. 그러나 메모리 사용은 인텔 포트란 컴파일러의 릴리스 모드에서 4GB였습니다. 그것은 8 배 더 큽니다!

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

파생 형식없이 할당 가능 또는 포인터 문을 사용할 때 메모리 누수가 발생하지 않습니다. 내 의견으로는, 파생 형에서 할당 가능 또는 포인터 형 변수를 선언하고 파생 형에서 할당 가능 변수가 아닌 파생 형 변수를 크게 할당하면 memeory leak이 발생합니다. 이 문제를 해결하기 위해 파생 코드를 포함하지 않는 코드를 다음 코드로 변경했습니다.

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

이 스타일은 어떻습니까?

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

NumNodes 변수는 각면의 노드 수를 의미하고 Node 변수는 NumNodes 변수와 일치하는 노드 번호입니다. 이 코드 스타일에서는 메모리 누수가 발생하지 않을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.