Использование threadpoolexecutor в сочетании с nogil Cython - PullRequest
0 голосов
/ 11 июня 2019

Я прочитал этот вопрос и ответ - Cython nogil с ThreadPoolExecutor не дает ускорений , и у меня похожая проблема с моим кодом Cython, не получающим ускорение, которое ожидается, несмотря на то, что моя система имеет несколько ядер. У меня есть 4 физических ядра на экземпляре Ubuntu 18.04, и если я сделаю количество заданий равным 1 в приведенном ниже коде, он будет работать быстрее, чем когда я его создаю. %. Я выполняю поиск структуры данных в классе C ++, который не изменяется, т. Е. Я делаю запросы только для чтения к структуре данных C ++ через Cython. На стороне C ++ нет блокировок мьютекса.

Это мой первый опыт работы с GIL, и мне интересно, правильно ли я его использовал. Кроме того, вывод времени немного сбивает с толку, так как я не думаю, что он правильно отображает фактическое время, затраченное каждым из рабочих потоков.

Кажется, я упустил что-то важное, но не могу понять, что это такое, поскольку я в значительной степени использовал тот же шаблон для использования GIL, как видно из связанного ответа SO.

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 Ответ

1 голос
/ 11 июня 2019

Мое предложение (в комментариях) состояло в том, чтобы убедиться, что весь цикл 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 имеет смысл для двумерного массива).

По сути, есть затраты на приобретение и выпуск GIL. Вы хотите убедиться, что работа, выполненная без GIL, стоит времени, потраченного на ее обработку. Простое вычисление площади прямоугольника довольно тривиально (два вычитания и умножение) и поэтому на самом деле не оправдывает время, затрачиваемое на координацию GIL между потоками - помните, что один раз каждый цикл должен (кратко) содержать GIL, во время которого время никакой другой поток не может держать его. Однако, когда весь цикл равен nogil, время, затрачиваемое на его администрирование, становится крошечным.

...