使用nogil处理Cython中的列表列表

时间:2018-08-21 18:08:36

标签: python multiprocessing cython gil

在Python中,我有一个列表列表作为输入:

input = [[0,1,2],[0,3,4,5],[0,6]]

实际上,子列表的数量是数万。每个子列表的长度可以有很大的不同,从零或一个值到数百个值。

我想将输入数据作为2D结构传递给处理它的Cython模块。我希望在多个内核上处理数据,因此我将prangenogil=True一起使用:

from cython.parallel import prange

cpdef int my_func(long[:,:] arr):
    cdef int i,j
    for i in prange(arr.shape[0], nogil=True):
        for j in range(arr.shape[1]):
                    # Do something
            pass
    return 42

我看到以下解决方案:

  1. 将列表列表放入2D ndarray。但是,由于每个子列表的长度差异很大,因此ndarray并不是理想的数据结构
  2. 修改my_func以接受列表列表。问题在于,部分代码是在没有GIL的情况下执行的,因此无法访问python对象。

有人对如何解决此问题有建议,最好是带有代码的建议?

1 个答案:

答案 0 :(得分:4)

我可能会选择一个扁平化的数组,其中单个列表的开始存储在一个辅助数组中,与csr-matrices相似。

这里是一个示例,该示例如何从列表列表构造此数据结构(使用numpy,但您也可以使用array.array;它实际上并没有针对速度进行优化),只是为了给您提供一个概念:

import numpy as np
def flatten_list_of_lists(lst_of_lsts):
    N = sum(map(len, lst_of_lsts))  # number of elements in the flattened array   
    starts = np.empty(len(lst_of_lsts)+1, dtype=np.uint64)  # needs place for one sentinel
    values = np.empty(N, dtype=np.int64)

    starts[0], cnt = 0, 0
    for i,lst in enumerate(lst_of_lsts):
        for el in lst:
            values[cnt] = el
            cnt += 1       # update index in the flattened array for the next element
        starts[i+1] = cnt  # remember the start of the next list

    return starts, values

因此对于您的示例,将产生以下结果:

#         starts                                 values
(array([0, 3, 7, 9], dtype=uint64), array([0, 1, 2, 0, 3, 4, 5, 0, 6]))

您会看到:有3个子列表,分别从0373(差异starts[1]-starts[0]),{{1} }和4个元素。

这是可以使用这些数据的方式:

2