0

我正在尝试通过使用 cython 功能来提高我的 python 代码的速度。我的python代码由类和py_child函数组成,如下所示:py_parentpy_backup

import random
from time import clock
import numpy as np
from libc.string cimport memcmp
## python code #################################################
class py_child:
    def __init__(self, move):
        self.move = move
        self.Q = 0
        self.N = 0

class py_parent:
    def __init__(self):
        self.children = []
    def add_children(self, moves):
        for move in moves:
            self.children.append(py_child(move))

def py_backup(parent, white_rave, black_rave):
    for point in white_rave:
        for ch in parent.children:
            if ch.move == point:
                ch.Q += 1
                ch.N += 1

    for point in black_rave:
        for ch in parent.children:
            if ch.move == point:
                ch.Q += 1
                ch.N += 1

这与cython使用 memoryviews 作为一些变量的实现相同:

## cython ######################################################

cdef class cy_child:
    cdef public:
        int[:] move
        int Q
        int N
    def __init__(self, move):
        self.move = move
        self.Q = 0
        self.N = 0

cdef class cy_parent:
    cdef public:
        list children
        int[:, :] moves
    def __init__(self):
        self.children = []
    def add_children(self, moves):
        cdef int i = 0
        cdef int N = len(moves)
        for i in range(N):
            self.children.append(cy_child(moves[i]))

cpdef cy_backup(cy_parent parent_node, int[:, :] white_rave,int[:, :] black_rave):
    cdef int[:] move
    cdef cy_child ch
    for move in white_rave:
        for ch in parent_node.children:
            if memcmp(&move[0], &ch.move[0], move.nbytes) == 0:
                ch.Q += 1
                ch.N += 1

    for move in black_rave:
        for ch in parent_node.children:
            if memcmp(&move[0], &ch.move[0], move.nbytes) == 0:
                ch.Q += 1
                ch.N += 1

现在我想评估函数 cy_backup、py_backup 的代码速度。所以我使用以下代码:

### Setup variables #########################################
size = 11
board = np.random.randint(2, size=(size, size), dtype=np.int32)

for x in range(board.shape[0]):
    for y in range(board.shape[1]):
        if board[x,y] == 0:
            black_rave.append((x,y))
        else:
            white_rave.append((x,y))

py_temp = []
for i in range(size):
    for j in range(size):
        py_temp.append((i,j))

#### python arguments #######################################

py = py_parent()
py.add_children(py_temp)
# also py_temp, black_rave, white_rave

#### cython arguments #######################################
cy_temp = np.assarray(py_temp, , dtype= np.int32)
cy_black_rave = np.asarray(black_rave, dtype= np.int32)
cy_white_rave = np.asarray(white_rave, dtype= np.int32)
cy = cy_parent()
cy.add_children(cy_temp)

#### Speed test #################################################
%timeit py_backup(py_parent, black_rave, white_rave)
%timeit cy_backup(cy_parent, cy_black_rave, cy_white_rave)

当我运行程序时,我对结果感到惊讶:

1000 loops, best of 3: 759 µs per loop
100 loops, best of 3: 6.38 ms per loop

我期待 cython 比 python 快得多,特别是在使用 memoryviews 时。
为什么 cython 中的循环运行速度比 python 中的循环慢?
如果有人对加速 cython 中的代码有任何建议,我们将不胜感激。
提前我为我的问题道歉,包括太多的代码。

4

1 回答 1

3

Cython 内存视图实际上只针对访问单个元素或切片(通常在循环中)的一件事进行了优化

# e.g.
cdef int i
cdef int[:] mview = # something
for i in range(mview.shape[0]):
   mview[i] # do some work with this....

这种类型的代码可以直接转换为高效的 C 代码。对于几乎任何其他操作,memoryview 都被视为 Python 对象。

不幸的是,几乎没有你的代码利用 memoryviews 擅长的一件事,所以你没有真正的加速。相反,它实际上更糟,因为您添加了一个额外的层,并且整个负载的小长度 2 内存视图将非常糟糕。

我的建议实际上只是使用列表——它们实际上非常适合这种事情,而且我完全不清楚如何重写你的代码以真正使用 Cython 加速它。


我发现了一些小的优化:通过查看由cython -a. 您会看到内存视图的一般迭代很慢(即纯 Python)。你通过改变得到改善

# instead of:
# for move in white_rave:
for i in range(white_rave.shape[0]):
    move = white_rave[i,:]

这让 Cython 以一种有效的方式迭代内存视图。

您可以通过关闭memcmp线路的一些安全检查来获得更快的速度:

with cython.boundscheck(False), cython.initializedcheck(False):
   if memcmp(&move[0], &ch.move[0], move.nbytes) == 0:

(你需要cimport cython)。如果你这样做并且你还没有初始化ch.move或者两个内存视图都没有至少一个元素,那么你的程序可能会崩溃。


我意识到这不是一个有用的答案,但只要你想保留child一个 Python 类(事件 a 类cdef),你真的无能为力来加速它。您可能会考虑将其更改为 C 结构(您可以拥有一个 C 数组),但随后您将失去使用 Python 的所有好处(即您必须管理自己的内存并且无法从 Python 代码轻松访问它)。

于 2017-08-07T18:50:17.270 回答