Матричный продукт 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)
,