当维度未知时,可以在Cython中使用Typed memoryview。

3

我想使用类型化的内存视图优化一个函数,但我不知道参数类型应该是什么。它可以是numpy数组,甚至可以是标量。那么,我该如何使用类型化的内存视图呢?


2
这个问题听起来有点像:“我想使用静态类型,但我在编译时不知道类型是什么”。 - cel
@cel 是的,听起来一样。 - bewithaman
1
我认为Cel可能在暗示你的两个要求是相互矛盾的。然而,一种方法是:1)定义一个“实现”函数,它操作一个1D memoryview。2)定义一个包装器函数,它可以操作任何Python对象。a)如果传递了一个1D memoryview,则调用实现函数;b)如果传递了一个标量,则创建一个1x1数组并调用实现函数;c)如果传递了一个多维数组,则将其展平以供实现函数使用,或者遍历行并为每行调用实现函数。 - DavidW
2
或者,您可以查找如何在Cython中制作numpy“ufuncs”,这可能是您想要的。但是,这可能非常棘手。 - DavidW
@DavidW 对我来说,你可以把你的评论作为答案添加进去...这基本上解决了OP的需求。 - Saullo G. P. Castro
@SaulloCastro 我当时没有将其发布为答案,因为我不想编写代码来说明它(我认为它可能需要代码才能成为一个合适的答案)。不过现在我已经发布了。 - DavidW
1个回答

2
这种问题的难点在于Python是动态类型的,因此在选择代码路径时总会失去速度。但是,从原则上讲,您可以使各个代码路径非常快。一个可能会给您带来良好结果的方法是:
  1. 定义一个“实现”函数,该函数操作1D memoryview。
  2. 定义一个包装器函数,该函数操作任何Python对象。
    1. 如果传递了1D memoryview,则调用实现函数;
    2. 如果传递了标量,则创建1x1数组并调用实现函数;
    3. 如果传递了多维数组,则将其展平以供实现函数使用,或者遍历行,在每行中调用实现函数。
下面是一个快速实现。这假定您想对输入数组的每个元素应用一个函数(并且希望输出一个相同大小的输出数组)。我选择的说明性函数只是将1添加到每个值。它还在某些地方使用了numpy(而不仅仅是typed memoryviews),我认为这是合理的:
cimport cython
import numpy as np
import numbers

@cython.boundscheck(False)
cdef double[:] _plus_one_impl(double[:] x):
  cdef int n
  cdef double[:] output

  output = x.copy()
  for n in range(x.shape[0]):
    output[n] = x[n]+1
  return output

def plus_one(x):
  if isinstance(x,numbers.Real): # check if it's a number
    return _plus_one_impl(np.array([x]))[0]
  else:
    try:
      return _plus_one_impl(x)
    except ValueError: # this gets thrown if conversion fails
      if len(x.shape)<2:
        raise ValueError('x could not be converted to double [:]')
      output = np.empty_like(x) # output is all numpy, whatever the input is
      for n in range(x.shape[0]): # this loop isn't typed, so is likely to be pretty slow
        output[n,...] = plus_one(x[n,...])
      return output

在某些情况下,这段代码可能会变得有些慢(即第二维度较短的2D数组)。

然而,我的真正建议是研究一下NumPy ufuncs,它们提供了一个有效地实现这种操作的接口。(请参阅http://docs.scipy.org/doc/numpy-dev/user/c-info.ufunc-tutorial.html)。不幸的是,它们比Cython更加复杂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接