Penggunaan memori dalam fortran saat menggunakan array tipe turunan dengan pointer

13

Dalam program sampel ini saya melakukan hal yang sama (setidaknya saya pikir begitu) dengan dua cara berbeda. Saya menjalankan ini pada pc Linux saya dan memantau penggunaan memori dengan top. Menggunakan gfortran, saya menemukan bahwa dengan cara pertama (antara "1" dan "2") memori yang digunakan adalah 8.2GB, sementara di jalan kedua (antara "2" dan "3") penggunaan memori adalah 3.0GB. Dengan kompiler Intel perbedaannya bahkan lebih besar: 10GB versus 3GB. Ini sepertinya penalti yang berlebihan untuk menggunakan pointer. Mengapa ini terjadi?

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

Latar belakangnya adalah penyempurnaan grid lokal. Saya memilih daftar yang ditautkan untuk dengan mudah menambah dan menghapus wajah. Jumlah node adalah 4 secara default tetapi bisa menjadi lebih tinggi tergantung pada penyempurnaan lokal.

chris
sumber
1
"Cara pertama" harus dihindari sebanyak mungkin karena rawan bocor (array harus secara eksplisit dialokasikan, seperti yang Anda lakukan) di samping perbedaan kinerja yang Anda lihat. Satu-satunya alasan untuk menggunakannya adalah karena kepatuhan yang ketat pada Fortran 95. Allocatable dalam tipe turunan ditambahkan dalam TR 15581 tetapi semua kompiler Fortran (bahkan yang tidak memiliki fitur 2003) telah mendukungnya, yaitu F95 + TR15581 + TR15580 sejak selamanya .
stali
1
Alasan untuk melakukannya adalah bahwa beberapa wajah mungkin memiliki lebih dari 4 node.
chris
Maka itu tentu masuk akal. Saya berasumsi bahwa 4 adalah konstan.
stali

Jawaban:

6

Saya sebenarnya tidak tahu bagaimana kompiler fortran bekerja, tetapi berdasarkan fitur bahasa, saya bisa menebak.

Array dinamis di fortran datang dengan meta-data untuk bekerja dengan fungsi intrinsik seperti bentuk, ukuran, lbound, ubound, dan dialokasikan atau terkait (dapat dialokasikan vs pointer). Untuk array besar, ukuran meta-data dapat diabaikan, tetapi untuk array kecil, seperti dalam kasus Anda, ini dapat bertambah. Dalam kasus Anda, array dinamis ukuran 4 cenderung memiliki lebih banyak meta-data daripada data nyata, yang mengarah ke balon penggunaan memori Anda.

Saya sangat merekomendasikan terhadap memori dinamis di bagian bawah struktur Anda. Jika Anda menulis kode yang berhubungan dengan sistem fisik dalam beberapa dimensi, Anda dapat mengaturnya sebagai makro dan mengkompilasi ulang. Jika Anda berurusan dengan grafik, Anda dapat secara statis mengalokasikan batas atas pada jumlah tepi atau sejenisnya. Jika Anda berurusan dengan sistem yang sebenarnya membutuhkan kontrol memori dinamis fine-grain, maka mungkin yang terbaik adalah beralih ke C.

Max Hutchinson
sumber
Ya tapi bukankah argumen metadata berlaku untuk kedua kasus?
stali
@stali tidak, perhatikan bahwa case kedua membutuhkan satu pointer, berlawanan dengan npointer yang dibutuhkan oleh metode pertama.
Aron Ahmadia
Saya telah menambahkan beberapa informasi latar belakang. Saran Anda untuk mengalokasikan batas atas secara statis sudah merupakan peningkatan yang baik. Batas atas adalah 8, tetapi mayoritas akan memiliki 4, hanya persentase kecil akan memiliki 5,6,7 atau 8. Jadi memori masih terbuang ...
chris
@ Chris: Bisakah Anda membuat dua daftar, satu dengan 4, dan satu dengan 8 node?
Pedro
Mungkin. Tampaknya ini kompromi yang bagus.
chris
5

Seperti yang ditunjukkan oleh maxhutch , masalahnya mungkin karena banyaknya alokasi memori yang terpisah. Namun, di atas metadata, mungkin ada data tambahan apa pun dan penyelarasan yang dibutuhkan oleh pengelola memori, yaitu mungkin membulatkan setiap alokasi hingga beberapa kelipatan 64 byte atau lebih.

Untuk menghindari mengalokasikan potongan kecil untuk setiap node, Anda dapat mencoba mengalokasikan setiap node sebagian dari array yang dialokasikan sebelumnya:

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

Fortran saya agak berkarat, tetapi di atas seharusnya bekerja, jika tidak pada prinsipnya.

Anda masih memiliki overhead apa pun yang menurut kompiler Fortran Anda perlukan untuk disimpan untuk tipe POINTER, tetapi Anda tidak akan memiliki overhead manajer memori.

Pedro
sumber
ini membantu tetapi hanya sedikit. Masalahnya adalah itu bukan pointer tunggal tetapi array dinamis dari pointer: FaceList (i)% node (1: FaceList (i)% nnodes) => theNodes (jari: jari + FaceList (i)% nnodes-1). Ini juga menyiratkan estimasi yang tajam untuk ukuran array yang dialokasikan sebelumnya.
chris
@ Chris: Saya tidak yakin saya benar-benar mengerti ... Apa yang Anda maksud dengan "array dinamis pointer"? Bidang nodesType%nodesadalah penunjuk ke array dinamis.
Pedro
0

Oh Ini adalah masalah yang sama yang saya alami. Pertanyaan ini sudah sangat lama, tetapi saya menyarankan gaya kode yang sedikit berbeda. Masalah saya adalah array pernyataan yang dapat dialokasikan dalam tipe data yang diturunkan, sebagai kode tindak.

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

Dari beberapa tes, saya mengkonfirmasi bahwa jika saya menggunakan pernyataan yang dapat dialokasikan atau pernyataan pointer dalam tipe turunan sebagai kode tindak tentang empat kasus, kebocoran memori terjadi sangat besar. Dalam kasus saya, saya merah file ukuran 520MB. Tetapi penggunaan memori adalah 4GB pada mode rilis pada komplier intel fortran. Itu 8 kali lebih besar!

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

Kebocoran memori tidak terjadi ketika saya menggunakan pernyataan yang dapat dialokasikan atau pointer tanpa tipe turunan. Menurut pendapat saya, jika saya mendeklarasikan variabel tipe pengalokasi atau penunjuk dalam tipe turunan dan besar mengalokasikan variabel tipe turunan bukan variabel yang dapat dialokasikan dalam tipe turunan, terjadi kebocoran memeory. Untuk mengatasi masalah ini, saya mengubah kode saya yang tidak menyertakan tipe turunan sebagai kode tindak.

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

atau bagaimana dengan gaya ini?

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

Variabel NumNodes berarti jumlah node pada setiap wajah dan variabel Node adalah jumlah simpul yang cocok dengan variabel NumNodes. Mungkin kebocoran memori tidak terjadi dalam gaya kode ini, saya pikir.

G. Ku
sumber