【问题标题】:Using a dictionary in Cython , especially inside nogil在 Cython 中使用字典,尤其是在 nogil 中
【发布时间】:2015-08-28 08:19:18
【问题描述】:

我有一本字典,

my_dict = {'a':[1,2,3], 'b':[4,5] , 'c':[7,1,2])

我想在 Cython nogil 函数中使用这本字典。所以,我试图将其声明为

cdef dict cy_dict = my_dict 

到这个阶段就好了。

现在我需要遍历 my_dict 的键,如果值在列表中,则遍历它。在 Python 中,这很简单:

 for key in my_dict:
      if isinstance(my_dict[key], (list, tuple)):
          ###### Iterate over the value of the list or tuple
          for value in list:
               ## Do some over operation.

但是,在 Cython 内部,我也想在 nogil 内部实现相同的功能。由于 nogil 内部不允许使用 python 对象,所以我都卡在这里了。

with nogil:
    #### same implementation of the same in Cython

谁能帮帮我?

【问题讨论】:

    标签: python cython gil


    【解决方案1】:

    如果没有 GIL,您将无法使用 Python dict,因为您可以使用它执行的所有操作都涉及操作 Python 对象。您最明智的选择是接受您需要 GIL。还有一个不太明智的选择涉及 C++ 映射,但可能很难适用于您的具体情况。

    您可以使用with gil: 重新获取 GIL。这里有明显的开销(使用 GIL 的部分不能并行执行,它可能会延迟等待 GIL)。但是,如果字典操作是一大段 Cython 代码的一小部分,这可能还不错:

    with nogil:
      # some large chunk of computationally intensive code goes here
      with gil:
        # your dictionary code
      # more computationally intensive stuff here
    

    另一个不太明智的选择是使用 C++ 映射(以及其他 C++ 标准库数据类型)。 Cython 可以包装这些并自动转换它们。根据您的示例数据举一个简单的例子:

    from libcpp.map cimport map
    from libcpp.string cimport string
    from libcpp.vector cimport vector
    from cython.operator cimport dereference, preincrement
    
    def f():
        my_dict = {'a':[1,2,3], 'b':[4,5] , 'c':[7,1,2]}
        # the following conversion has an computational cost to it 
        # and must be done with the GIL. Depending on your design
        # you might be able to ensure it's only done once so that the
        # cost doesn't matter much
        cdef map[string,vector[int]] m = my_dict
        
        # cdef statements can't go inside no gil, but much of the work can
        cdef map[string,vector[int]].iterator end = m.end()
        cdef map[string,vector[int]].iterator it = m.begin()
            
        cdef int total_length = 0
        
        with nogil: # all  this stuff can now go inside nogil   
            while it != end:
                total_length += dereference(it).second.size()
                preincrement(it)
            
        print total_length
    

    (你需要用language='c++'编译它)。

    这样做的明显缺点是必须事先知道 dict 中的数据类型(它不能是任意 Python 对象)。但是,由于您无法在 nogil 块内操作任意 Python 对象,因此无论如何您都受到了很大的限制。

    6 年后附录:我不建议将“在所有地方使用 C++ 对象”方法作为一般方法。 Cython-C++ 接口有点笨拙,你可以花很多时间来解决它。 Python 容器实际上比你想象的要好。每个人都倾向于忘记将 C++ 对象转换为 Python 对象的成本。 People rarely consider if they really need to release the GIL or if they just read an article on the internet somewhere saying that the GIL is bad..

    这对某些任务有好处,但在盲目地将所有 list 替换为 vectordict 替换为 map 等之前,请仔细考虑。通常,如果您的 C++ 类型完全存在于您的函数中,它 可能是个不错的举动(但请三思……)。如果它们被转换为输入或输出参数,请考虑第三次。

    【讨论】:

    • 谢谢。让我看看。字典中的数据类型,我将其保留为列表。对于编译,我可以按照正常的 Cython 方式进行吗?
    • 您需要按照here 的描述指定语言,否则它应该可以正常工作。
    • 哦,很抱歉。我删除了评论,因为我发现它有效。很抱歉提到这一点
    • cdef map[string,vector[string]].iterator end = m.end() 。这条线发生了什么?我试图打印结束。但是出现了一些错误
    • C++ 标准库容器最容易被迭代器访问,您可以通过取消引用它们来获取内容(C++ 中的*it),并且您可以通过递增它们来扫描(++it C++)。 end 是每个容器都有的特殊迭代器。它本身实际上并不有效,因此您无法取消引用它,但您可以使用它来判断您何时遍历了另一个迭代器 (while it != end) 的所有值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    相关资源
    最近更新 更多