Матричный продукт Fortran замедляется при вызове с помощью f2py через python

Я пытался использовать f2py для взаимодействия оптимизированного кода Fortran для векторного и матричного умножения с Python. Чтобы получить сравнение производительности, полезное для моих целей, я выполняю один и тот же продукт внутри цикла 100000 раз. С полным кодом Fortran продукт занимает 2,4 секунды (ifort), в то время как с F2py это занимает около 11 секунд. Просто для справки, с NumPy это занимает около 20 секунд. Я прошу и фортран, и часть Python записать разницу во времени до и после цикла, и с помощью f2py они пишут по 11 секунд, поэтому код не теряет время при передаче массивов. Я пытался понять, является ли это способом хранения массива numpy, но я не могу понять проблему. Есть ли у вас какие-либо идеи? заранее спасибо

Фортран Майн

program Main
    implicit none
    save

    integer :: seed, i, j, k
    integer, parameter :: states =15
    integer, parameter :: tessere = 400
    real, dimension(tessere,states,states) :: matrix
    real, dimension(states) :: vector
    real :: start, finish
    real  :: prod(tessere)

    do i=1,tessere
       do j=1,states
          do k=1,states
              matrix(i,j,k) = i+j+k
          end do
       enddo
    end do
    do i=1,states
        vector(i) = i
    enddo
    call doubleSum(vector,vector,matrix,states,tessere,prod)

end program

фортран подпрограмма:

subroutine doubleSum(ket, bra, M , states, tessere,prod)
    integer :: its, j, k,t
    integer :: states
    integer :: tessere
    real, dimension(tessere,states,states) :: M
    real, dimension(states) :: ket
    real, dimension(states) :: bra
    real, dimension(tessere) :: prod
    real,dimension(tessere,states) :: ctmp

    call cpu_time(start)
    do t=1,100000
        ctmp=0.d0
        do k=1,states
             do j=1,states
                do its=1,tessere
                   ctmp(its,k)=ctmp(its,k)+ M(its,k,j)*ket(j)
                enddo
             enddo
        enddo
        do its=1,tessere
            prod(its)=dot_product(bra,ctmp(its,:))
        enddo
    enddo
    call cpu_time(finish)
    print '("Time = ",f6.3," seconds.")',finish-start
end subroutine

скрипт на питоне

import numpy as np
import time
import cicloS


M= np.random.rand(400,15,15)
ket=np.random.rand(15)

#M=np.asfortranarray(M)
#ket=np.asfortranarray(ket)

import time


start=time.time()  
prod=cicloS.doublesum(ket,ket,M)
end=time.time()
print(end-start)

Файл.pyf, созданный с помощью f2py и отредактированный

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module cicloS 
    interface  
        subroutine doublesum(ket,bra,m,states,tessere,prod) 
            real dimension(states),intent(in) :: ket
            real dimension(states),depend(states),intent(in) :: bra
            real dimension(tessere,states,states),depend(states,states),intent(in) :: m
            integer, optional,check(len(ket)>=states),depend(ket) :: states=len(ket)
            integer, optional,check(shape(m,0)==tessere),depend(m) :: tessere=shape(m,0)
            real dimension(tessere),intent(out) :: prod
        end subroutine doublesum
    end interface
end python module cicloS

1 ответ

Решение

OP указал, что наблюдаемая разница во времени выполнения между автономными и скомпилированными версиями кода F2PY была вызвана использованием различных компиляторов и флагов компилятора.

Чтобы получить согласованный результат и тем самым ответить на вопрос, необходимо убедиться, что F2PY использует требуемый 1) компилятор и 2) флаги компилятора.

Часть 1: Укажите, какой компилятор Фортрана должен использоваться F2PY

Список компиляторов Fortran, доступных для F2PY в целевой системе, можно отобразить, выполнив, например, python -m numpy.f2py -c --help-fcompiler, В моей системе это приводит к усечению:

Fortran compilers found:
  --fcompiler=gnu95    GNU Fortran 95 compiler (7)
  --fcompiler=intelem  Intel Fortran Compiler for 64-bit apps (19.0.1.144)

Вы можете указать F2PY, какой из доступных компиляторов Fortran использовать, добавив соответствующий --fcompiler флаг вашей команды компиляции. Для использования ifort например (при условии, что вы уже создали и отредактировали cicloS.pyf файл):

python -m numpy.f2py --fcompiler=intelem -c cicloS.pyf sub.f90

Часть 2. Укажите дополнительные флаги компилятора, которые будут использоваться F2PY

Обратите внимание, что выход из --help-fcompiler на предыдущем шаге также отображаются флаги компилятора по умолчанию (см., например, compiler_f90), что F2PY определяет для каждого доступного компилятора. Снова в моей системе это было (усечено и упрощено для большинства соответствующих флагов):

  • gnu95: -O3 -funroll-loops
  • intelem: -O3 -xSSE4.2 -axCORE-AVX2,COMMON-AVX512

Вы можете указать предпочтительные флаги оптимизации для F2PY с помощью --opt флаг в вашей команде компиляции (см. также --f90flags в документации), что теперь становится, например:

python -m numpy.f2py --fcompiler=intelem --opt='-O1' -c cicloS.pyf sub.f90

Сравните время выполнения для автономной версии и версии F2PY:

Компиляция отдельного исполняемого файла с ifort -O1 sub.f90 main.f90 -o mainи скомпилированная версия F2PY из части 2, я получаю следующий вывод:

./main
Time =  5.359 seconds.

python test.py
Time =  5.297 seconds.
5.316878795623779

Затем, компилируя автономный исполняемый файл с ifort -O3 sub.f90 main.f90 -o mainи (по умолчанию) скомпилированная версия F2PY из части 1, я получаю следующие результаты:

./main
Time =  1.297 seconds.

python test.py
Time =  1.219 seconds.
1.209657907485962

Таким образом, показаны аналогичные результаты для автономной версии и версии F2PY, а также влияние флагов компилятора.

Комментарий к временным массивам

Хотя это не является причиной замедления, которое вы наблюдаете, обратите внимание, что F2PY вынужден делать временные копии массивов. M (а также ket) в вашем примере Python по двум причинам:

  • 3D массив M что вы передаете cicloS.doublesum() является массивом NumPy по умолчанию с порядком Си (мажор-строка). Поскольку Fortran использует упорядочение по главному столбцу, F2PY сделает копии массива. Закомментированный np.asfortranarray() следует исправить эту часть проблемы.
  • следующая причина для массива копий (также для ket) существует несоответствие между реальными типами на Python (по умолчанию 64-битная, с плавающей запятой двойной точности) и Fortran (real дает точность по умолчанию, вероятно, 32-битные плавающие) стороны вашего примера. Таким образом, копии снова сделаны для учета этого.

Вы можете получить уведомление о копировании массива, добавив -DF2PY_REPORT_ON_ARRAY_COPY=1 флаг (также в документации) для вашей команды компиляции F2PY. В вашем случае копий массивов можно полностью избежать, изменив dtype вашей M а также ket матрицы в Python (т.е. M=np.asfortranarray(M, dtype=np.float32)) а также ket=np.asfortranarray(ket, dtype=np.float32)) или, альтернативно, определив real переменные в вашем коде Fortran с соответствующими kind (например, добавить use, intrinsic :: iso_fortran_env, only : real64 к вашей подпрограмме и основной программе и определить реал с real(kind=real64),

Другие вопросы по тегам