9

我刚刚使用 F2PY 将 Fortran 90 子例程包装到 python 中。这里的微妙之处在于 Fortran 子例程也将 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 函数非常耗时,那么有什么方法可以提高包装模块的速度吗?

4

1 回答 1

9

在您发布的代码中,a是双精度浮点数。将它从 Fortran 传递到 Python 意味着将 Fortran double 包装到 PyFloat 对象,这确实是有代价的。在纯 Python 版本中,k 是一个 PyFloat,您无需为包装 1000 次而付出代价。

另一个问题是函数调用本身。从 C 调用 Python 函数在性能方面已经很差了,但从 Fortran 调用它们更糟糕,因为有一层额外的代码可以将 Fortran 函数调用约定(关于堆栈等)转换为 C 函数调用约定。从 C 调用 Python 函数时,需要将参数准备为 Python 对象,一般创建一个 PyTuple 对象作为 Python 函数的 *args 参数,在模块的表中查找以获取函数指针。 ..

最后但同样重要的是:在 Fortran 和 Numpy 之间传递二维数组时,您需要注意数组顺序。F2py 和 numpy 在这方面可能很聪明,但是如果您的 Python 代码不是为了按 Fortran 顺序操作数组而编写的,那么您会受到性能影响。

我不知道 pyfunc 是什么意思,但如果它接近你发布的内容,用 Python 编写循环,并且只调用一次函数会节省你的时间。如果您需要中间值 ( p),让您的 Python 函数返回一个包含所有中间值的 Numpy 数组。

于 2011-09-23T06:27:11.177 回答