并行化python:多处理与cython

时间:2017-02-07 11:59:46

标签: python numpy parallel-processing cython

我想并行化一个迭代,其中评估了许多cython实例实例,结果存储在一个全局的numpy数组中:

for cythonInstance in myCythonInstances:
    success = cythonInstance.evaluate(someConstantGlobalVariables,) # very CPU intense
    if success == False:
        break
    globalNumpyArray[instanceSpecificLocation] = cythonInstance.resultVector[:]

实例评估的结果彼此独立。实例之间没有任何类型的交互,除了结果写入相同的全局数组,但是在固定的,预定的和独立的位置。如果一个评估失败,则必须停止迭代。

据我所知,有两种可能: 1)使用多处理包 2)制作一个cython函数并使用prange / openmp。

我根本没有并行化的经验。哪种解决方案更可取,还是有更好的替代方案?谢谢!

2 个答案:

答案 0 :(得分:5)

如果可以,请使用Cython:

  1. prange语法与range非常相似。它允许您采用编写Python循环的简单开发途径 - >将其转换为Cython - >将其转换为并行循环。希望每次所需的更改都很小。相比之下,多处理要求您将循环内部作为一个函数,然后设置ppols,因此它不那么熟悉。

  2. OpenMP / Cython线程开销很低。相比之下,多处理模块的开销相对较高("流程"通常慢于"线程")。

  3. 多处理在Windows中受到很大限制(一切都必须是可选择的)。这经常是一件非常麻烦的事。

  4. 在您应该使用多处理时,有一些特定情况:

    1. 你发现你需要获得很多GIL - 多处理并不共享GIL所以不会减慢速度。如果你偶尔只需要获得GIL,那么Cython中的with gil:小块通常不会让你减速太多,所以先试试吧。

    2. 您需要同时执行一系列完全不同的操作(即,某些东西不适合prange循环,因为每个线程都在真正运行单独的代码)。如果是这种情况,Cython prange语法并不合适。

    3. 查看代码的注意事项是,如果可以的话,应该避免使用Cython类。如果你可以将它重构为一个更好的cdef函数调用(Cython类有时候仍然需要GIL)。类似于以下内容的方法效果很好:

      cdef int f(double[:] arr, double loop_specific_parameter, int constant_parameter) nogil:
          # return your boolean to stop the iteration
          # modify arr
          return result
      
      # then elsewhere
      cdef int i
      cdef double[:,:] output = np.zeros(shape)
      for i in prange(len(parameters_to_try),nogil=True):
         result = f(output[i,:],parameters_to_try[i],constant_parameter)
         if result:
            break
      

      我不建议使用Cython类的原因是1)你不能创建它们或索引它们的列表而没有GIL(出于参考计数的原因)和2)包括Cython在内的Python对象班级似乎不允许是本地线程。有关问题的示例,请参阅Cython parallel prange - thread locality?。 (原来我并不知道成为当地人的限制)

      所涉及的with_gil开销并不一定很大,所以如果这个设计最有意义,那就试试吧。查看您的CPU使用情况将告诉您它的并行化程度。

      的Nb。 this set of answers中的大部分优缺点仍然适用,即使您使用的是Cython而不是Python线程模块。不同之处在于您通常可以避免使用Cython中的GIL(因此使用线程的一些缺点不太重要)。

答案 1 :(得分:2)

我建议将joblibthreading后端一起使用。 Joblib是一个非常好的forlelellize for循环工具。 Joblib
线程优先于多处理,因为多处理有很多开销。当需要进行大量并行计算时,这将是不适当的。 结果存储在列表中,然后您可以将其转换回numpy数组。

from joblib import Parallel, delayed

def sim(x):
    return x**2   


if __name__ == "__main__":

    result = Parallel(n_jobs=-1, backend="threading", verbose=5) \
        (delayed(sim)(x) for x in range(10))

    print result

结果

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]