2015-11-15 14 views
76

Tôi nhận được một mảng 512^3 đại diện cho phân bố Nhiệt độ từ mô phỏng (được viết bằng Fortran). Mảng được lưu trữ trong một tệp nhị phân có kích thước khoảng 1/2G. Tôi cần phải biết tối thiểu, tối đa và có nghĩa là của mảng này và như tôi sẽ sớm cần phải hiểu mã Fortran anyway, tôi quyết định để cho nó một đi và đến với thói quen rất dễ dàng sau đây.Làm thế nào để có thể nhanh hơn nhiều so với thường lệ của Fortran?

integer gridsize,unit,j 
    real mini,maxi 
    double precision mean 

    gridsize=512 
    unit=40 
    open(unit=unit,file='T.out',status='old',access='stream',& 
     form='unformatted',action='read') 
    read(unit=unit) tmp 
    mini=tmp 
    maxi=tmp 
    mean=tmp 
    do j=2,gridsize**3 
     read(unit=unit) tmp 
     if(tmp>maxi)then 
      maxi=tmp 
     elseif(tmp<mini)then 
      mini=tmp 
     end if 
     mean=mean+tmp 
    end do 
    mean=mean/gridsize**3 
    close(unit=unit) 

Điều này mất khoảng 25 giây cho mỗi tệp trên máy tôi sử dụng. Đánh tôi như là khá dài và vì vậy tôi đã đi trước và đã làm như sau trong Python:

import numpy 

    mmap=numpy.memmap('T.out',dtype='float32',mode='r',offset=4,\ 
            shape=(512,512,512),order='F') 
    mini=numpy.amin(mmap) 
    maxi=numpy.amax(mmap) 
    mean=numpy.mean(mmap) 

Bây giờ, tôi nghĩ đây chỉ là nhanh hơn tất nhiên, nhưng tôi đã thực sự thổi bay đi. Phải mất ít hơn một giây trong điều kiện giống hệt nhau. Giá trị trung bình bị lệch so với mức phát hiện thường thấy của Fortran (mà tôi cũng chạy với các phao 128 bit, vì vậy bằng cách nào đó tôi tin tưởng nó nhiều hơn) nhưng chỉ trên số có nghĩa thứ 7 hoặc hơn.

Làm thế nào để có thể quá nhanh? Tôi có nghĩa là bạn phải xem xét mọi mục nhập của một mảng để tìm các giá trị này, đúng không? Tôi đang làm điều gì đó rất ngu ngốc trong thói quen Fortran của tôi cho nó mất nhiều thời gian hơn?

EDIT:

Để trả lời các câu hỏi trong các ý kiến:

  • Vâng, tôi cũng chạy thường xuyên Fortran với 32-bit và 64-bit nổi nhưng nó đã không ảnh hưởng đến hiệu suất .
  • Tôi đã sử dụng iso_fortran_env cung cấp phao nổi 128 bit.
  • Sử dụng phao 32 bit, giá trị trung bình của tôi bị giảm đi một chút, vì vậy độ chính xác thực sự là vấn đề.
  • Tôi chạy cả hai thường trình trên các tệp khác nhau theo thứ tự khác nhau, vì vậy bộ nhớ đệm nên có công bằng trong so sánh tôi đoán?
  • Tôi thực sự đã thử mở MP, nhưng để đọc từ tệp tại các vị trí khác nhau cùng một lúc. Có đọc ý kiến ​​của bạn và câu trả lời này âm thanh thực sự ngu ngốc bây giờ và nó làm cho thói quen mất nhiều thời gian là tốt. Tôi có thể cung cấp cho nó một thử trên các hoạt động mảng nhưng có lẽ thậm chí sẽ không cần thiết.
  • Các tệp thực sự có kích thước 1/2G, đó là lỗi đánh máy, Cảm ơn.
  • Tôi sẽ thử triển khai mảng ngay bây giờ.

EDIT 2:

tôi thực hiện những gì @Alexander Vogt và @ Casey gợi ý trong câu trả lời của họ, và nó là nhanh như numpy nhưng bây giờ tôi có một vấn đề chính xác như @Luaan chỉ ra tôi có thể được. Sử dụng mảng phao 32 bit, giá trị trung bình tính bằng sum là 20%. Làm

... 
real,allocatable :: tmp (:,:,:) 
double precision,allocatable :: tmp2(:,:,:) 
... 
tmp2=tmp 
mean=sum(tmp2)/size(tmp) 
... 

Giải quyết vấn đề nhưng tăng thời gian tính toán (không phải rất nhiều, nhưng đáng chú ý). Có cách nào tốt hơn để giải quyết vấn đề này không? Tôi không thể tìm được cách để đọc đĩa đơn từ tập tin trực tiếp để tăng gấp đôi. Và làm thế nào để numpy tránh điều này?

Cảm ơn tất cả sự trợ giúp từ trước tới nay.

+10

Bạn đã thử thường trình Fortran không có nổi 128 bit chưa? Tôi không biết bất kỳ phần cứng nào thực sự hỗ trợ những phần cứng đó, vì vậy chúng phải được thực hiện trong phần mềm. – user2357112

+4

Điều gì sẽ xảy ra nếu bạn thử phiên bản Fortran bằng một mảng (và cụ thể là sử dụng một lần đọc thay vì một tỷ)? – francescalus

+9

Bạn có cân nhắc sử dụng toán tử mảng trong Fortran không? Sau đó, bạn có thể thử 'minval()', 'maxval()', và 'sum()'?Hơn nữa, bạn đang trộn IO với các hoạt động trong Fortran, nhưng không phải bằng Python - đó không phải là sự so sánh công bằng ;-) –

Trả lời

108

thực hiện Fortran của bạn bị hai thiếu sót lớn:

  • Bạn trộn IO và tính toán (và đọc từ mục tập tin bằng cách nhập cảnh).
  • Bạn không sử dụng các hoạt động vector/ma trận.

thực hiện này không thực hiện các hoạt động tương tự như của bạn và nhanh hơn bởi một yếu tố của 20 trên máy tính của tôi:

program test 
    integer gridsize,unit 
    real mini,maxi,mean 
    real, allocatable :: tmp (:,:,:) 

    gridsize=512 
    unit=40 

    allocate(tmp(gridsize, gridsize, gridsize)) 

    open(unit=unit,file='T.out',status='old',access='stream',& 
     form='unformatted',action='read') 
    read(unit=unit) tmp 

    close(unit=unit) 

    mini = minval(tmp) 
    maxi = maxval(tmp) 
    mean = sum(tmp)/gridsize**3 
    print *, mini, maxi, mean 

end program 

Ý tưởng là để đọc trong toàn bộ file vào một mảng tmp trong một đi . Sau đó, tôi có thể sử dụng các hàm MAXVAL, MINVALSUM trên mảng trực tiếp.


Đối với các vấn đề chính xác: Đơn giản chỉ cần sử dụng các giá trị chính xác đôi và thực hiện chuyển đổi một cách nhanh chóng như

mean = sum(real(tmp, kind=kind(1.d0)))/real(gridsize**3, kind=kind(1.d0)) 

chỉ nhỉnh làm tăng thời gian tính toán. Tôi đã thử thực hiện phần tử hoạt động khôn ngoan và trong các lát, nhưng điều đó chỉ làm tăng thời gian cần thiết ở mức tối ưu hóa mặc định.

Tại -O3, phần bổ sung khôn ngoan thực hiện ~ 3% tốt hơn hoạt động mảng. Sự khác biệt giữa các hoạt động chính xác đôi và đơn lẻ nhỏ hơn 2% trên máy tính của tôi - tính trung bình (cá nhân chạy lệch nhiều hơn).


Đây là một thực hiện rất nhanh sử dụng LAPACK:

program test 
    integer gridsize,unit, i, j 
    real mini,maxi 
    integer :: t1, t2, rate 
    real, allocatable :: tmp (:,:,:) 
    real, allocatable :: work(:) 
! double precision :: mean 
    real :: mean 
    real :: slange 

    call system_clock(count_rate=rate) 
    call system_clock(t1) 
    gridsize=512 
    unit=40 

    allocate(tmp(gridsize, gridsize, gridsize), work(gridsize)) 

    open(unit=unit,file='T.out',status='old',access='stream',& 
     form='unformatted',action='read') 
    read(unit=unit) tmp 

    close(unit=unit) 

    mini = minval(tmp) 
    maxi = maxval(tmp) 

! mean = sum(tmp)/gridsize**3 
! mean = sum(real(tmp, kind=kind(1.d0)))/real(gridsize**3, kind=kind(1.d0)) 
    mean = 0.d0 
    do j=1,gridsize 
    do i=1,gridsize 
     mean = mean + slange('1', gridsize, 1, tmp(:,i,j),gridsize, work) 
    enddo !i 
    enddo !j 
    mean = mean/gridsize**3 

    print *, mini, maxi, mean 
    call system_clock(t2) 
    print *,real(t2-t1)/real(rate) 

end program 

này sử dụng ma trận chính xác duy nhất 1-norm SLANGE trên các cột ma trận. Thời gian chạy thậm chí còn nhanh hơn so với cách tiếp cận sử dụng các hàm mảng chính xác đơn lẻ - và không hiển thị vấn đề chính xác.

+4

Tại sao trộn đầu vào với tính toán làm chậm quá nhiều? Cả hai đều phải đọc toàn bộ tập tin, đó sẽ là nút cổ chai. Và nếu hệ điều hành không đọc được, mã Fortran không cần phải chờ đợi nhiều cho I/O. – Barmar

+2

@Barmar Bạn vẫn sẽ có chức năng gọi trên không và logic để kiểm tra xem dữ liệu có trong bộ nhớ cache mỗi lần không. – Overv

54

Nhanh nhẹn nhanh hơn vì bạn đã viết mã hiệu quả hơn nhiều trong python (và nhiều phần phụ trợ được viết bằng Fortran và C tối ưu) và mã không hiệu quả khủng khiếp trong Fortran.

Nhìn mã python của bạn. Bạn tải toàn bộ mảng cùng một lúc và sau đó gọi các hàm có thể hoạt động trên một mảng.

Xem mã fortran của bạn. Bạn đọc một giá trị tại một thời điểm và làm một số logic phân nhánh với nó.

Phần lớn sự khác biệt của bạn là IO phân mảnh mà bạn đã viết ở Fortran.

Bạn có thể viết Fortran giống như cách bạn viết python và bạn sẽ thấy nó chạy nhanh hơn nhiều theo cách đó.

program test 
    implicit none 
    integer :: gridsize, unit 
    real :: mini, maxi, mean 
    real, allocatable :: array(:,:,:) 

    gridsize=512 
    allocate(array(gridsize,gridsize,gridsize)) 
    unit=40 
    open(unit=unit, file='T.out', status='old', access='stream',& 
     form='unformatted', action='read') 
    read(unit) array  
    maxi = maxval(array) 
    mini = minval(array) 
    mean = sum(array)/size(array) 
    close(unit) 
end program test 
+0

Liệu giá trị trung bình được tính theo cách này có được độ chính xác giống như lệnh '.mean' của' numpy' không? Tôi có một số nghi ngờ về điều đó. – Bakuriu

+1

@ Bakuriu Không, không. Xem câu trả lời của Alexander Vogt và các chỉnh sửa của tôi về câu hỏi. – user35915

Các vấn đề liên quan