threadpoolexecutor与cython的nogil结合使用

时间:2019-06-11 06:46:13

标签: python-3.x multithreading performance cython gil

我已经阅读了此问题和答案-Cython nogil with ThreadPoolExecutor not giving speedups,尽管我的系统具有多个内核,但我的Cython代码也没有获得预期的加速,这也有一个类似的问题。我在Ubuntu 18.04实例上有4个物理核心,如果我在下面的代码中将作业数设为1,则它的运行速度比使它运行时的速度快。4。使用顶部查看CPU使用率,我看到CPU使用率上升到300 %。我正在不修改的C ++类中查找数据结构,即我仅通过Cython对C ++数据结构进行只读查询。 C ++端没有任何互斥锁。

这是我第一次使用GIL,我想知道我是否使用不正确。时间的输出也有些混乱,因为我认为它不能正确地描述每个工作线程所花费的实际时间。

我似乎错过了一些关键的东西,但是我无法弄清楚它是什么,因为我已经使用了与链接的SO答案相同的GIL用法模板。

import psutil
import numpy as np

from concurrent.futures import ThreadPoolExecutor
from functools import partial



cdef extern from "Rectangle.h" namespace "shapes":
cdef cppclass Rectangle:
    Rectangle(int, int, int, int)
    int x0, y0, x1, y1
    int getArea() nogil


cdef class PyRectangle:
     cdef Rectangle *rect 

def __cinit__(self, int x0, int y0, int x1, int y1):
    self.rect = new Rectangle(x0, y0, x1, y1)

def __dealloc__(self):
    del self.rect

def testThread(self):

    latGrid = np.arange(minLat,maxLat,0.05)
    lonGrid = np.arange(minLon,maxLon,0.05)

    gridLon,gridLat = np.meshgrid(latGrid,lonGrid)
    grid_points = np.c_[gridLon.ravel(),gridLat.ravel()]

    n_jobs = psutil.cpu_count(logical=False)

    chunk = np.array_split(grid_points,n_jobs,axis=0)
    x = ThreadPoolExecutor(max_workers=n_jobs) 

    t0 = time.time()
    func = partial(self.performCalc,maxDistance)
    results = x.map(func,chunk)
    results = np.vstack(list(results))
    t1 = time.time()
    print(t1-t0)

def performCalc(self,maxDistance,chunk):

    cdef int area
    cdef double[:,:] gPoints
    gPoints = memoryview(chunk)
    for i in range(0,len(gPoints)):
        with nogil:
            area =  self.getArea2(gPoints[i])
    return area

cdef int getArea2(self,double[:] p) nogil :
    cdef int area
    area = self.rect.getArea()
    return area

1 个答案:

答案 0 :(得分:1)

我的建议(在评论中)是确保整个performCalc循环为nogil。为此,需要进行一些更改:

cdef Py_ssize_t i # set type of "i" (although Cython can possibly deduce this anyway)
with nogil:
    for i in range(0,gPoints.shape[0]):
        area =  self.getArea2(gPoints[i])

其中最重要的是将len(gPoints)交换为gPoints.shape[0],这用数组查找替换了对Python函数的调用(我个人也不认为len对于一个2D数组。

从本质上说,获取和发布GIL会产生成本。您要确保没有GIL的工作值得花时间处理。简单地计算一个矩形的面积是微不足道的(两个减法和一个乘法),因此并不能真正证明花费在协调线程之间的GIL上的时间-请记住,每个循环一旦执行,每个线程必须(简短地)保持GIL,在此期间时间没有其他线程可以容纳它。但是,以整个循环为nogil来管理它所花费的时间变得很小。