Numpy for Loop的Cython优化

时间:2018-08-21 14:28:58

标签: python numpy optimization cython

我是cython的新手,并为我试图优化的numpy for循环提供了以下代码。到目前为止,此Cython代码并不比numpy for循环快得多。

# cython: infer_types = True
import numpy as np
cimport numpy

DTYPE = np.double

def hdcfTransfomation(scanData):
    cdef Py_ssize_t Position
    scanLength = scanData.shape[0]
    hdcfFunction_np = np.zeros(scanLength, dtype = DTYPE)
    cdef double [::1] hdcfFunction = hdcfFunction_np

    for position in range(scanLength - 1):
        topShift = scanData[1 + position:]
        bottomShift = scanData[:-(position + 1)]
        arrayDiff = np.subtract(topShift, bottomShift)
        arraySquared = np.square(arrayDiff)
        arrayMean = np.mean(arraySquared, axis = 0)
        hdcfFunction[position] = arrayMean
    return hdcfFunction

我知道使用C数学库函数比调用numpy语言(减,平方,均值)更理想,但是我不确定在哪里可以找到可以这种方式调用的函数列表。

我一直在尝试找出通过使用不同类型等来优化此代码的方法。但没有什么能提供我认为完全优化的Cython实现所能提供的性能。

以下是numpy for循环的工作示例:

def hdcfTransfomation(scanData):
    scanLength = scanData.shape[0]
    hdcfFunction = np.zeros(scanLength)

    for position in range(scanLength - 1):
        topShift = scanData[1 + position:]
        bottomShift = scanData[:-(position + 1)]
        arrayDiff = np.subtract(topShift, bottomShift)
        arraySquared = np.square(arrayDiff)
        arrayMean = np.mean(arraySquared, axis = 0)
        hdcfFunction[position] = arrayMean
    return hdcfFunction

scanDataArray = np.random.rand(80000, 1)
transformedScan = hdcfTransformed(scanDataArray)

2 个答案:

答案 0 :(得分:0)

cython放在一边,这是否与您当前的代码相同,但没有for循环?我们可以收紧它并纠正错误,但首先要进行的工作是尝试将numpy中的操作应用于2D数组,然后再对cython循环使用for。评论太久了。

import numpy as np

# Setup
arr = np.random.choice(np.arange(10), 100).reshape(10, 10)

top_shift = arr[:, :-1]
bottom_shift = arr[:, 1:]
arr_diff = top_shift - bottom_shift
arr_squared = np.square(arr_diff)
arr_mean = arr_squared.mean(axis=1)

答案 1 :(得分:0)

始终提供尽可能多的信息(一些示例数据,Python / Cython版本,编译器版本/设置和CPU模型。

没有时间比较任何时间都非常困难。例如,此问题从SIMD矢量化中受益匪浅。使用哪种编译器,或者如果您想重新分发也应该在低端或相当老的CPUS(例如,没有AVX)上运行的编译版本,将会大有不同。

我对Cython不太熟悉,但是我认为您的主要问题是scanData缺少声明。也许C编译器需要诸如march=native之类的其他标志,但是真正的语法是编译器dependend。我也不确定Cython或C编译器如何优化此部分:

    arrayDiff = np.subtract(topShift, bottomShift)
    arraySquared = np.square(arrayDiff)
    arrayMean = np.mean(arraySquared, axis = 0)

如果未加入该循环(所有矢量化命令实际上都是循环),但实际上存在像创建纯Python一样的临时arryas,这将减慢代码速度。首先创建一个1D数组是一个好主意。 (例如scanData=scanData[::1]

正如我所说的,我对Cython并不熟悉,所以我尝试了Numba的可能。至少它显示了合理的Cycyon实施也应该有的可能。

对于编译器来说可能更容易优化

import numba as nb
import numpy as np

@nb.njit(fastmath=True,error_model='numpy',parallel=True)
#scanData is a 1D-array here
def hdcfTransfomation(scanData):
  scanLength = scanData.shape[0]
  hdcfFunction = np.zeros(scanLength, dtype = scanData.dtype)

  for position in nb.prange(scanLength - 1):
    topShift = scanData[1 + position:]
    bottomShift = scanData[:scanData.shape[0]-(position + 1)]
    sum=0.
    jj=0
    for i in range(scanLength-(position + 1)):
      jj+=1
      sum+=(topShift[i]-bottomShift[i])**2

    hdcfFunction[position] = sum/jj
  return hdcfFunction

我在这里也使用了并行化,因为这个问题令人尴尬地是并行的。至少使用80_000和Numba的大小,无论您使用的是经过稍微修改的代码版本(一维数组)还是上面的代码都没关系。

时间

#Quadcore Core i7-4th gen,Numba 0.4dev,Python 3.6
scanData=np.random.rand(80_000)
#The first call to the function isn't measured (compilation overhead),but the following calls.

Pure Python:           5900ms
Numba single-threaded: 947ms
Numba parallel:        260ms

特别是对于比np.random.rand(80_000)大的数组,可能会有更好的方法(循环耕作以更好地使用缓存),但是对于这个大小应该差不多(至少适合L3缓存)< / p>

天真的GPU实现

from numba import cuda, float32
@cuda.jit('void(float32[:], float32[:])')
def hdcfTransfomation_gpu(scanData,out_data):
  scanLength = scanData.shape[0]
  position = cuda.grid(1)

  if position < scanLength - 1:
    sum= float32(0.)
    offset=1 + position
    for i in range(scanLength-offset):
      sum+=(scanData[i+offset]-scanData[i])**2
      out_data[position] = sum/(scanLength-offset)

hdcfTransfomation_gpu[scanData.shape[0]//64,64](scanData,res_3)

这在GT640(float32)和970ms(float64)上给出了大约400ms。为了获得良好的实现,应该考虑shared arrays