使用Python在Windows上实现并发/并行

使用Python在Windows上实现并发/并行,python,windows,multiprocessing,Python,Windows,Multiprocessing,我开发了一个简单的程序来解决八皇后问题。现在我想用不同的元参数做更多的测试,所以我想让它更快。我进行了几次分析迭代,能够显著缩短运行时间,但我认为只有部分计算同时进行才能加快速度。我尝试使用多处理和并发.futures模块,但它并没有显著提高运行时,在某些情况下甚至会降低执行速度。这只是提供一些背景 我能够提出类似的代码结构,其中顺序版本优于并发版本 import numpy as np import concurrent.futures import math import time impo

我开发了一个简单的程序来解决八皇后问题。现在我想用不同的元参数做更多的测试,所以我想让它更快。我进行了几次分析迭代,能够显著缩短运行时间,但我认为只有部分计算同时进行才能加快速度。我尝试使用
多处理
并发.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上使用
python3.6.4 | Anaconda,Inc.
同时运行它,我是否能够以某种方式使它更快。我在这里读到,在Windows上创建新进程是昂贵的。有什么办法可以加快速度吗?我错过了什么明显的东西吗

我还试着只创建一次
,但似乎没有多大帮助


编辑:

原始代码结构大致如下所示:

我的代码的结构大致如下:

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
元素。无法提前告知算法收敛的速度和执行的迭代次数,可能是数千次。

在UNIX变体下,进程要轻得多。Windows进程很繁重,启动时间要长得多。线程是在windows上执行多处理的推荐方法。 您也可以遵循此线程:

您的设置对多处理不太公平。你甚至包括了不必要的
prime=None
赋值

有几点:


数据大小

您生成的数据是一种很小的方法,可以让流程创建的开销得到回报。尝试使用
范围(1\u 000\u 000)
而不是
范围(5000)
。在具有
多处理.start\u方法的Linux上
设置为“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部分能够释放全局解释器锁()。这意味着,您可以从多核并行性中获益,而无需增加进程创建的开销。如果你在做数学,尽量利用numpy。尝试
concurrent.futures.ThreadPoolExecutor
multi-processing.dummy.Pool
,代码使用numpy。

使用numba或Cython。你问题中的链接与OP问题中的链接相同……因此,我认为这没有多大帮助,由于GIL和CPU的限制,线程对IO绑定的任务很有帮助。是的,确实如此,线程比进程轻。尝试一下,你会看到不同之处。感谢您花时间研究这个问题,并在Linux上运行代码。我目前没有访问任何Linux环境的权限,但我很想看看那里的数字如何。我之所以使用包含5000个元素的数组,是因为它更好地反映了原始代码中发生的情况,同时也显示了运行时的差异。我修改了这个问题来描述它。还有一点,我正在重用
,以使代码保持可读性。多亏了Numpy,我能够显著提高代码其他部分的性能,因此我非常欣赏它提供的性能提升,但我认为我不能将其用于其余部分。在这一点上,我很想知道Windows上多处理的局限性是什么。可能是因为对于包含
1000
元素的列表,创建新流程毫无意义,因为开销太大了吗?@Grzegorz这与列表大小无关,而是与按顺序处理它所需的时间有关。如果这只需要几毫秒,开销就无法支付。不仅创建一个新流程,而且仅仅将工作发送到现有池中都要付出代价,因为已经对数据进行酸洗和取消酸洗需要一些时间。您必须权衡串行计算持续时间与mp开销,看看是否值得。@Grzegorz仅供参考:在具有分叉的Linux上,我得到的范围(5000),473个素数:并发:0.013890/顺序:0.008119/多处理:0.105427,范围(1_000_000),89479个素数:并发:0.97016/顺序:1.263134/多处理:0.611853