Параллелизм / параллелизм в Windows с Python - PullRequest
0 голосов
/ 09 сентября 2018

Я разработал простую программу для решения проблемы восьми королев. Теперь я хотел бы провести еще несколько тестов с различными мета-параметрами, поэтому я хотел бы сделать это быстро. Я прошел несколько итераций профилирования и смог значительно сократить время выполнения, но я достиг точки, когда я считаю, что только части вычислений одновременно могут сделать это быстрее. Я пытался использовать модули multiprocessing и concurrent.futures, но это не сильно улучшало время выполнения, а в некоторых случаях даже замедляло выполнение. То есть просто дать некоторый контекст.

Мне удалось придумать похожую структуру кода, где последовательная версия превосходит параллельную.

import numpy as np
import concurrent.futures
import math
import time
import multiprocessing

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def generate_data(seed):
    np.random.seed(seed)
    numbers = []
    for _ in range(5000):
        nbr = np.random.randint(50000, 100000)
        numbers.append(nbr)
    return numbers

def run_test_concurrent(numbers):
    print("Concurrent test")
    start_tm = time.time()
    chunk = len(numbers)//3
    primes = None
    with concurrent.futures.ProcessPoolExecutor(max_workers=3) as pool:
        primes = list(pool.map(is_prime, numbers, chunksize=chunk))
    print("Time: {:.6f}".format(time.time() - start_tm))
    print("Number of primes: {}\n".format(np.sum(primes)))


def run_test_sequential(numbers):
    print("Sequential test")
    start_tm = time.time()
    primes = [is_prime(nbr) for nbr in numbers]
    print("Time: {:.6f}".format(time.time() - start_tm))
    print("Number of primes: {}\n".format(np.sum(primes)))


def run_test_multiprocessing(numbers):
    print("Multiprocessing test")
    start_tm = time.time()
    chunk = len(numbers)//3
    primes = None
    with multiprocessing.Pool(processes=3) as pool:
        primes = list(pool.map(is_prime, numbers, chunksize=chunk))
    print("Time: {:.6f}".format(time.time() - start_tm))
    print("Number of primes: {}\n".format(np.sum(primes)))


def main():
    nbr_trails = 5
    for trail in range(nbr_trails):
        numbers = generate_data(trail*10)
        run_test_concurrent(numbers)
        run_test_sequential(numbers)
        run_test_multiprocessing(numbers)
        print("--\n")


if __name__ == '__main__':
    main()

Когда я запускаю его на своем компьютере - Windows 7, Intel Core i5 с четырьмя ядрами, я получаю следующий вывод:

Concurrent test
Time: 2.006006
Number of primes: 431

Sequential test
Time: 0.010000
Number of primes: 431

Multiprocessing test
Time: 1.412003
Number of primes: 431
--

Concurrent test
Time: 1.302003
Number of primes: 447

Sequential test
Time: 0.010000
Number of primes: 447

Multiprocessing test
Time: 1.252003
Number of primes: 447
--

Concurrent test
Time: 1.280002
Number of primes: 446

Sequential test
Time: 0.010000
Number of primes: 446

Multiprocessing test
Time: 1.250002
Number of primes: 446
--

Concurrent test
Time: 1.260002
Number of primes: 446

Sequential test
Time: 0.010000
Number of primes: 446

Multiprocessing test
Time: 1.250002
Number of primes: 446
--

Concurrent test
Time: 1.282003
Number of primes: 473

Sequential test
Time: 0.010000
Number of primes: 473

Multiprocessing test
Time: 1.260002
Number of primes: 473
--

Вопрос, который у меня возникает, состоит в том, могу ли я сделать это как-то быстрее, запустив его одновременно на Windows с Python 3.6.4 |Anaconda, Inc.|. Я читал здесь о SO ( Почему создание нового процесса в Windows дороже, чем Linux? ), что создание новых процессов в Windows дорого. Есть ли что-нибудь, что можно сделать, чтобы ускорить процесс? Я что-то упускаю из виду?

Я также пытался создать Pool только один раз, но это не очень помогло.


Edit:

Исходная структура кода выглядит примерно так:

Мой код более или менее похож на структуру:

class Foo(object):

    def g() -> int:
        # function performing simple calculations
        # single function call is fast (~500 ms)
        pass


def run(self):
    nbr_processes = multiprocessing.cpu_count() - 1

    with multiprocessing.Pool(processes=nbr_processes) as pool:
        foos = get_initial_foos()

        solution_found = False
        while not solution_found:
            # one iteration
            chunk = len(foos)//nbr_processes
            vals = list(pool.map(Foo.g, foos, chunksize=chunk))

            foos = modify_foos()

с foos, имеющим 1000 элементов. Невозможно заранее определить, насколько быстро сходятся алгоритмы и сколько итераций выполняется, возможно, тысячи.

Ответы [ 2 ]

0 голосов
/ 10 сентября 2018

Ваша настройка не совсем справедлива для многопроцессорной обработки. Вы даже включили ненужные primes = None задания. ;)

Некоторые баллы:


Размер данных

Ваши сгенерированные данные - это путь к тому, чтобы вернуть затраты на создание процесса. Попробуйте с range(1_000_000) вместо range(5000). В Linux с multiprocessing.start_method, установленным в 'spawn' (по умолчанию в Windows), это рисует другую картину:

Concurrent test
Time: 0.957883
Number of primes: 89479

Sequential test
Time: 1.235785
Number of primes: 89479

Multiprocessing test
Time: 0.714775
Number of primes: 89479

Повторное использование вашего бассейна

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


Numpy

Numpy частично может снять глобальную блокировку интерпретатора ( GIL ). Это означает, что вы можете извлечь выгоду из многоядерного параллелизма без затрат на создание процессов. В любом случае, если вы занимаетесь математикой, постарайтесь максимально использовать numy. Попробуйте concurrent.futures.ThreadPoolExecutor и multiprocessing.dummy.Pool с кодом, используя numpy.

0 голосов
/ 09 сентября 2018

Процессы намного легче в вариантах UNIX. Процессы Windows тяжелы и требуют гораздо больше времени для запуска. Потоки - это рекомендуемый способ многопроцессорной обработки в Windows. Вы также можете следовать этой теме: Почему создание нового процесса в Windows дороже, чем в Linux?

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