使用python多处理优化一个简单的CPU绑定函数

使用python多处理优化一个简单的CPU绑定函数,python,performance,numpy,parallel-processing,python-multiprocessing,Python,Performance,Numpy,Parallel Processing,Python Multiprocessing,我试图理解multiprocessing.Pool是如何工作的,我开发了一个简单的示例来说明我的问题。简单地说,我使用pool.map通过以下示例并行化在阵列上运行的CPU绑定函数。当我遵循这种模式时,我只有4个内核的适度加速,但是如果我手动将数组分块到num_线程中,然后在这些分块上使用pool.map,我会发现加速因子远远超过4倍,这对我来说毫无意义。详情如下 首先,函数定义 def take_up_time(): n = 1e3 while n > 0:

我试图理解multiprocessing.Pool是如何工作的,我开发了一个简单的示例来说明我的问题。简单地说,我使用pool.map通过以下示例并行化在阵列上运行的CPU绑定函数。当我遵循这种模式时,我只有4个内核的适度加速,但是如果我手动将数组分块到num_线程中,然后在这些分块上使用pool.map,我会发现加速因子远远超过4倍,这对我来说毫无意义。详情如下

首先,函数定义

def take_up_time():
    n = 1e3
    while n > 0:
        n -= 1

def count_even_numbers(x):
    take_up_time()
    return np.where(np.mod(x, 2) == 0, 1, 0)
现在定义我们将基准测试的函数

首先是以串行方式运行的函数:

def serial(arr):
    return np.sum(map(count_even_numbers,arr))
现在,以“标准”方式使用Pool.map的函数:

最后是第二种策略,我手动将数组分块,然后在分块上运行Pool.map(由于

这是我的数组输入:

npts = 1e3
arr = np.arange(npts)
现在我使用IPython%timeit函数来运行计时,对于1e3点,我得到以下结果:

  • 串行:10个环路,每个环路3:98.7毫秒的最佳时间
  • 并行化策略1:10个循环,每个循环的最佳时间为3:77.7毫秒
  • 并行化策略2:10个循环,每个循环最好3:22毫秒
因为我有4个核心,所以策略1的加速效果令人失望,而策略2的加速效果令人怀疑地大于最大4倍的加速效果

当我将NPT增加到1e4时,结果更令人困惑:

  • 串行:1个循环,每个循环最好3:967毫秒
  • 并行化策略1:1个循环,每个循环最好3:596毫秒
  • 并行化策略2:10个循环,每个循环的最佳时间为3:22.9毫秒
因此,混淆的两个来源是:

  • 策略2比天真的理论极限快得多
  • 出于某种原因,npts=1e4的%timeit仅触发串行和策略1的1个循环,但触发策略2的10个循环

  • 你的策略不一样

    在第一种策略中,
    Pool.map
    迭代一个数组,因此为每个数组项调用
    count\u偶数
    (因为数组的形状是一维的)


    第二个策略映射到一个数组列表上,因此对列表中的每个数组调用
    计数偶数。

    您的策略不一样

    在第一种策略中,
    Pool.map
    迭代一个数组,因此为每个数组项调用
    count\u偶数
    (因为数组的形状是一维的)


    第二种策略映射到一个数组列表,因此对列表中的每个数组调用
    计数偶数。

    结果表明,您的示例非常适合该模型。编译以下源代码
    count\u偶数.py

    #pythran export count_even(int [:])
    import numpy as np
    
    def count_even_numbers(x):
        return np.where(np.mod(x, 2) == 0, 1, 0)
    
    def count_even(arr):
        s = 0
        #omp parallel for reduction(+:s)
        for elem in arr:
            s += count_even_numbers(elem)
        return s
    
    使用命令行(-fopenmp激活注释的处理):

    由于转换为本机代码,在此基础上运行
    timeit
    已经产生了巨大的加速:

    没有Pythran

    $ python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    verryyy long, more than several minutes :-/
    
    使用Pytran,一个核心

    $ OMP_NUM_THREADS=1 python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    100 loops, best of 3: 10.3 msec per loop
    
    配备Pyran,两个磁芯

    $ OMP_NUM_THREADS=2 python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    100 loops, best of 3: 5.5 msec per loop
    
    并行化速度提高了一倍:-)


    请注意,OpenMP支持多线程,而不是多处理。

    结果表明,您的示例非常适合该模型。编译以下源代码
    count\u偶数.py

    #pythran export count_even(int [:])
    import numpy as np
    
    def count_even_numbers(x):
        return np.where(np.mod(x, 2) == 0, 1, 0)
    
    def count_even(arr):
        s = 0
        #omp parallel for reduction(+:s)
        for elem in arr:
            s += count_even_numbers(elem)
        return s
    
    使用命令行(-fopenmp激活注释的处理):

    由于转换为本机代码,在此基础上运行
    timeit
    已经产生了巨大的加速:

    没有Pythran

    $ python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    verryyy long, more than several minutes :-/
    
    使用Pytran,一个核心

    $ OMP_NUM_THREADS=1 python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    100 loops, best of 3: 10.3 msec per loop
    
    配备Pyran,两个磁芯

    $ OMP_NUM_THREADS=2 python -m timeit -s 'import numpy as np; arr = np.arange(1e7, dtype=int); from count_even import count_even' 'count_even(arr)'
    100 loops, best of 3: 5.5 msec per loop
    
    并行化速度提高了一倍:-)


    请注意,OpenMP支持多线程,而不是多处理。

    您是否比较了不同策略的结果?您是否比较了不同策略的结果?罗兰,这一点很好,解决了这个谜团,事后看来很明显。谢谢罗兰,这是一个很好的观点,它解决了这个谜,而且在事后看来是显而易见的。谢谢