我使用F2PY将Fortran 90子程序包装到python中。这里的细微之处在于Fortran子例程aslo将python回调函数作为其参数之一:
SUBROUTINE f90foo(pyfunc, a)
real(kind=8),intent(in) :: a
!f2py intent(callback) pyfunc
external pyfunc
!f2py real*8 y,x
!f2py y = pyfunc(x)
!*** debug begins***
print *, 'Start Loop'
do i=1,1000
p = pyfunc(a)
end do
total = etime(elapsed)
print *, 'End: total=', total, ' user=', elapsed(1), ' system=', elapsed(2)
stop
!*** debug ends ***
pyfunc
是我的python代码中别处定义的python函数。包装器工作正常,但运行上面的包装版本,我得到的时间比使用纯python的时间长5倍,如下所示,
def pythonfoo(k):
""" k: scalar
returns: scalar
"""
print('Pure Python: Start Loop')
start = time.time()
for i in xrange(1000):
p = pyfunc(k)
elapsed = (time.time() - start)
print('End: total=%20f'% elapsed)
所以,问题是,来自哪里的开销是多少?我真的想离开pyfunc
,因为将它重新编码为纯fortran函数是非常耗时的,所以有没有办法提高包装模块的速度?
答案 0 :(得分:9)
在您发布的代码中,a
是双精度浮点数。将它从Fortran传递给Python意味着将Fortran double包装到PyFloat对象,这确实有成本。在纯Python版本中,k是一个PyFloat,你不需要为它包装1000次付出代价。
另一个问题是函数调用本身。从C调用Python函数已经在性能方面表现不佳,但是从Fortran调用它们更糟糕,因为还有一层额外的代码可以将Fortran函数调用约定(关于堆栈等)转换为C函数调用约定。从C调用Python函数时,需要将参数准备为Python对象,通常创建一个PyTuple对象作为Python函数的* args参数,在模块的表中进行查找以获取函数指针。 ..
最后但同样重要的是:在Fortran和Numpy之间传递2D数组时,需要处理数组顺序。在这方面,F2py和numpy可以很聪明,但是如果没有编写Python代码来操作Fortran命令中的数组,那么你将获得性能命中。
我不知道pyfunc是做什么的,但如果它接近你发布的内容,在Python中编写循环,只调用一次函数将节省你的时间。如果你需要中间值(p
),让你的Python函数返回一个包含所有中间值的Numpy数组。