Многопроцессорный пул не меняет скорость обработки? - PullRequest
2 голосов
/ 07 мая 2019

Я создал изображение, аппроксимирующее генетический алгоритм, используя python 3 и opencv. Что он делает, так это создает популяцию людей, которые рисуют на чистом изображении круги произвольного цвета, размера и непрозрачности. В конце концов, сильнейшие насытят население через несколько сотен поколений.

Я попытался реализовать многопроцессорность, потому что рендеринг изображений требует времени, соответствующего размеру популяции и размеру круга, а также размеру целевого изображения (важно для детализации деталей)

Я использовал многопроцессорную обработку и пул с массивом отдельных объектов в качестве итерируемого и отображал только пригодность и идентификатор. По сути, в основном процессе ни у одного из участников нет собственного холста, тогда как в многопроцессных процессах каждый из них визуализирует свой холст и вычисляет пригодность / разницу.

Однако, похоже, что многопроцессорность замедляет работу всей программы? На самом деле процесс рендеринга, похоже, требует той же скорости по сравнению с сериализованной обработкой, но медленнее из-за многопроцессорности.

class PopulationCircle:
    def renderPop(self, individual):
        individual.render()
    return [individual.index, individual.fitness]
class IndividualCircle:
    def render(self):
        self.genes.sort(key=lambda x: x[-1], reverse=False)
        self.canvas = np.zeros((self.height,self.width, 4), np.uint8)
        for i in range(self.maxCount):
            overlay=self.canvas.copy()
            cv2.circle(overlay, (self.genes[i][0], self.genes[i][1]), self.genes[i][2], (self.genes[i][3],self.genes[i][4],self.genes[i][5]), -1, lineType=cv2.LINE_AA)
            self.canvas = cv2.addWeighted(overlay, self.genes[i][6], self.canvas, 1-self.genes[i][6], 0)

        diff = np.absolute(np.array(self.target)- np.array(self.canvas))

        diffSum = np.sum(diff)

        self.fitness = diffSum

def evolution(mainPop, generationLimit):
    p = mp.Pool()

    for i in range(int(generationLimit)):
        start_time = time.time()
        result =[]
        print(f"""
-----------------------------------------
Current Generation: {mainPop.generation}
Initial Score: {mainPop.score}
-----------------------------------------
        """)

        #Multiprocessing used for rendering out canvas since it takes time.

        result = p.map(mainPop.renderPop, mainPop.population)

        #returns [individual.index, individual.fitness]; results is a list of list
        result.sort(key = lambda x: x[0], reverse=False)

        #Once multiprocessing is done, we only receive fitness value and index. 
        for k in mainPop.population:
            k.fitness = result[k.index][1]
        mainPop.population.sort(key = lambda x: x.fitness, reverse = True)
        if mainPop.generation == 0:
            mainPop.score = mainPop.population[-1].fitness

        """
        Things to note:
            In main process, none of the individuals have a canvas since the rendering
            is done on a different process tree.
            The only thing that changes in this main process is the individual's 
            fitness.

            After calling .renderHD and .renderLD, the fittest member will have a canvas
            drawn in this process. 
        """

        end_time = time.time() - start_time
        print(f"Time taken: {end_time}")
        if i%50==0:
            mainPop.population[0].renderHD()
            cv2.imwrite( f"../output/generationsPoly/generation{i}.jpg", mainPop.population[0].canvasHD)

        if i%10==0:
            mainPop.population[0].renderLD()
            cv2.imwrite( f"../output/allGenPoly/image{i}.jpg", mainPop.population[0].canvas)

        mainPop.toJSON()
        mainPop.breed()



    p.close()
    p.join()

if __name__ == "__main__":
        #Creates Population object
        #init generates self.population array which is an array of IndividualCircle objects that contain DNA and render methods
    pop = PopulationCircle(targetDIR, maxPop, circleAmount, mutationRate, mutationAmount, cutOff)
    #Starts loop
    evolution(pop, generations)

если я использую 600 населения с 800 кругами, Серийный номер взял: 11сегментация, ср. многопроцессорность: 18 с / итерация, средняя.

Я очень плохо знаком с многопроцессорностью, поэтому любая помощь будет принята.

1 Ответ

1 голос
/ 07 мая 2019

Причина, по которой это происходит, заключается в том, что opencv порождает много потоков.Когда вы отключаетесь от основного и запускаете несколько процессов, каждый из этих процессов создает отдельную группу потоков opencv, что приводит к небольшой лавине. Проблема заключается в том, что они в конечном итоге синхронизируют и ждут снятия блокировки, чего выможно легко проверить, профилировав код с помощью cProfile.

Проблема описана в joblib документах.Это также вероятно ваше решение: переключиться на joblib.У меня была похожая проблема в прошлом, вы найдете ее в этом SO сообщении .

[РЕДАКТИРОВАТЬ] Дополнительное доказательство и решение здесь .Короче говоря, согласно этому посту, это известная проблема, но так как opencv выпускает GIL, можно запустить многопоточность вместо многопроцессорной обработки и, следовательно, сократить накладные расходы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...