Sử dụng bộ nhớ trong fortran khi sử dụng một mảng loại dẫn xuất với con trỏ


13

Trong chương trình mẫu này, tôi đang làm điều tương tự (ít nhất là tôi nghĩ vậy) theo hai cách khác nhau. Tôi đang chạy cái này trên máy tính Linux của tôi và theo dõi việc sử dụng bộ nhớ hàng đầu. Sử dụng gfortran tôi thấy rằng theo cách thứ nhất (giữa "1" và "2") bộ nhớ được sử dụng là 8.2GB, trong khi ở cách thứ hai (giữa "2" và "3"), mức sử dụng bộ nhớ là 3.0GB. Với trình biên dịch Intel, sự khác biệt thậm chí còn lớn hơn: 10 GB so với 3 GB. Điều này có vẻ như một hình phạt quá mức cho việc sử dụng con trỏ. Lý do tại sao điều này xảy ra?

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

Nền là sàng lọc lưới địa phương. Tôi đã chọn danh sách liên kết để dễ dàng thêm và xóa khuôn mặt. Số lượng nút là 4 theo mặc định nhưng có thể trở nên cao hơn tùy thuộc vào các sàng lọc cục bộ.


1
"Cách đầu tiên" nên tránh càng nhiều càng tốt vì nó dễ bị rò rỉ (các mảng phải được giải quyết rõ ràng, như bạn đã làm) bên cạnh sự khác biệt về hiệu suất mà bạn thấy. Lý do duy nhất để sử dụng nó là vì tuân thủ nghiêm ngặt Fortran 95. Các loại có nguồn gốc được phân bổ đã được thêm vào TR 15581 nhưng tất cả các trình biên dịch Fortran (ngay cả những tính năng không có bất kỳ tính năng 2003 nào) đã hỗ trợ chúng, ví dụ như F95 + TR15581 + TR15580 .
stali

1
Lý do để làm điều đó là một số khuôn mặt có thể có nhiều hơn 4 nút.
chris

Sau đó, nó chắc chắn có ý nghĩa. Tôi giả sử rằng 4 là một hằng số.
stali

Câu trả lời:


6

Tôi thực sự không biết trình biên dịch fortran hoạt động như thế nào, nhưng dựa trên các tính năng ngôn ngữ, tôi có thể đoán.

Các mảng động trong fortran đi kèm với siêu dữ liệu để hoạt động với các chức năng nội tại như hình dạng, kích thước, liên kết, ubound và phân bổ hoặc liên kết (phân bổ so với con trỏ). Đối với các mảng lớn, kích thước của dữ liệu meta là không đáng kể, nhưng đối với các mảng nhỏ, như trong trường hợp của bạn, nó có thể tăng lên. Trong trường hợp của bạn, mảng động kích thước 4 có thể có nhiều dữ liệu meta hơn dữ liệu thực, điều này dẫn đến bong bóng sử dụng bộ nhớ của bạn.

Tôi thực sự khuyên bạn nên chống lại bộ nhớ động ở dưới cùng của cấu trúc của bạn. Nếu bạn đang viết mã liên quan đến các hệ thống vật lý ở một số kích thước, bạn có thể đặt mã đó dưới dạng macro và biên dịch lại. Nếu bạn xử lý các biểu đồ, bạn có thể phân bổ tĩnh một giới hạn trên cho số cạnh hoặc số lượt thích. Nếu bạn đang làm việc với một hệ thống thực sự cần điều khiển bộ nhớ động hạt mịn, thì có lẽ tốt nhất là chuyển sang C.


Vâng, nhưng không phải đối số siêu dữ liệu đúng cho cả hai trường hợp?
stali

@stali không, lưu ý rằng trường hợp thứ hai yêu cầu một con trỏ, trái ngược với các ncon trỏ cần thiết của phương thức đầu tiên.
Aron Ahmadia

Tôi đã thêm một số thông tin cơ bản. Đề xuất của bạn để phân bổ tĩnh một giới hạn trên đã là một cải tiến tốt. Giới hạn trên là 8, nhưng phần lớn sẽ có 4, chỉ một tỷ lệ nhỏ sẽ có 5,6,7 hoặc 8. Vì vậy, bộ nhớ vẫn bị lãng phí ...
chris

@chris: Bạn có thể tạo hai danh sách, một với 4 và một với 8 nút không?
Pedro

Có lẽ. Nó có vẻ là một thỏa hiệp tốt.
chris

5

Như maxhutch đã chỉ ra, vấn đề có lẽ là số lượng phân bổ bộ nhớ riêng biệt. Tuy nhiên, trên đầu siêu dữ liệu, có thể có bất kỳ dữ liệu bổ sung nào và căn chỉnh trình quản lý bộ nhớ cần, nghĩa là có thể làm tròn mỗi phân bổ lên đến một số bội từ 64 byte trở lên.

Để tránh phân bổ một đoạn nhỏ cho mỗi nút, bạn có thể thử phân bổ cho mỗi nút một phần của mảng được phân bổ trước:

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 của tôi là một chút gỉ, nhưng ở trên nên hoạt động, nếu không về nguyên tắc.

Bạn vẫn có các tổng phí cho bất cứ thứ gì trình biên dịch Fortran của bạn nghĩ rằng nó cần lưu trữ cho loại POINTER, nhưng bạn sẽ không có các chi phí quản lý bộ nhớ.


Điều này giúp nhưng chỉ một chút. Vấn đề là nó không phải là một con trỏ đơn mà là một mảng con trỏ động: FaceList (i)% nút (1: FaceList (i)% nnodes) => theNodes (finger: finger + FaceList (i)% nnodes-1). Nó cũng ngụ ý một ước tính sắc nét cho kích thước của mảng được phân bổ trước.
chris

@chris: Tôi không chắc là tôi hoàn toàn hiểu ... Ý của bạn là gì về một "con trỏ động"? Trường này nodesType%nodeslà một con trỏ tới một mảng động.
Pedro

0

Oh. Đây là cùng một vấn đề tôi phải chịu. Câu hỏi này rất cũ, nhưng tôi đề nghị một chút phong cách mã khác nhau. Vấn đề của tôi là mảng câu lệnh phân bổ trong kiểu dữ liệu dẫn xuất, như mã sau.

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

Từ một số thử nghiệm, tôi đã xác nhận rằng nếu tôi sử dụng câu lệnh phân bổ hoặc câu lệnh con trỏ trong loại dẫn xuất như mã theo sau về bốn trường hợp, rò rỉ bộ nhớ xảy ra rất lớn. Trong trường hợp của tôi, tôi đỏ tập tin có kích thước 520MB. Nhưng mức sử dụng bộ nhớ là 4GB ở chế độ phát hành trên intel fortran. Đó là lớn hơn 8 lần!

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

Rò rỉ bộ nhớ không xảy ra khi tôi sử dụng câu lệnh phân bổ hoặc con trỏ mà không có kiểu dẫn xuất. Theo tôi, nếu tôi khai báo biến loại phân bổ hoặc loại con trỏ trong loại dẫn xuất và phân bổ lớn biến loại dẫn xuất không phân bổ biến trong loại dẫn xuất, xảy ra rò rỉ memeory. Để giải quyết vấn đề này, tôi đã thay đổi mã không bao gồm loại dẫn xuất như mã theo sau.

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

hoặc làm thế nào về phong cách này?

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

Biến NumNodes có nghĩa là số lượng nút trên mỗi mặt và biến Node là số nút khớp với biến NumNodes. Có lẽ rò rỉ bộ nhớ không xảy ra theo kiểu mã này, tôi nghĩ vậy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.