python - 为什么我的并行性能达到顶峰?

标签 python performance parallel-processing multiprocessing

我最近经常使用 Python,在比较众多并行化包时,我注意到从串行到并行的性能提升似乎在 6 个进程而不是 8 个进程时达到顶峰——我的 MacBook 的核心数量Pro (OS X 10.8.2) 有。

所附图表比较了不同任务的时间作为进程数(并行或顺序)的函数。这个例子使用的是 python 内置的'multiprocessing '包“内存”与“处理器”指的是内存密集型(仅分配大型数组)与计算密集型(许多操作)功能。

8道以下出顶的原因是什么?

enter image description here

(“时间”是每个进程数 100 次函数调用的平均值)

import multiprocessing as mp
import time
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt

iters       = 100
mem_num     = 1000
pro_num     = 20000
max_procs   = 10

line_width  = 2.0
legend_size = 10
fig_name    = 'timing.pdf'

def UseMemory(num):
    test1 = np.zeros([num,num])
    test2 = np.arange(num*num)
    test3 = np.array(test2).reshape([num, num])
    test4 = np.empty(num, dtype=object)
    return 

def UseProcessor(num):
    test1 = np.arange(num)
    test1 = np.cos(test1)
    test1 = np.sqrt(np.fabs(test1))
    test2 = np.zeros(num)
    for i in range(num): test2[i] = test1[i]
    return np.std(test2)

def MemJob(its): 
    for ii in range(its): UseMemory(mem_num)

def ProJob(its): 
    for ii in range(iters): UseProcessor(pro_num)


if __name__ == "__main__":

    print '\nParTest\n'    

    proc_range = np.arange(1,max_procs+1,step=1)

    test_times = np.zeros([len(proc_range),2,2])                 # test_times[num_procs][0-ser,1-par][0-mem,1-pro]
    tot_times  = np.zeros([len(proc_range),2  ])                 #  tot_times[num_procs][0-ser,1-par]

    print ' Testing %2d numbers of processors between [%d,%d]' % (len(proc_range), 1, max_procs)
    print ' Iterations %d, Memory Length %d, Processor Length %d' % (iters, mem_num, pro_num)

    for it in range(len(proc_range)):
        procs = proc_range[it]
        job_arg = procs*[iters]
        print '\n - %2d, Processes = %3d' % (it, procs)

        # --- Test Serial ---
        print ' - - Serial'
        # Test Memory
        all_start = time.time()
        start = time.time()
        map(MemJob, [procs*iters])
        ser_mem_time = time.time() - start

        # Test Processor
        start = time.time()
        map(ProJob, job_arg)
        ser_pro_time = time.time() - start

        ser_time = time.time() - all_start

        # --- Test Parallel : multiprocessing ---
        print ' - - Parallel: multiprocessing'
        pool = mp.Pool(processes=procs)
        # Test Memory
        all_start = time.time()
        start = time.time()
        pool.map(MemJob, job_arg)
        par_mem_time = time.time() - start

        # Test Processor
        start = time.time()
        pool.map(ProJob, job_arg)
        par_pro_time = time.time() - start

        par_time = time.time() - all_start

        print ' - - Collecting'
        ser_mem_time /= procs
        ser_pro_time /= procs
        par_mem_time /= procs
        par_pro_time /= procs
        ser_time     /= procs
        par_time     /= procs

        test_times[it][0] = [ ser_mem_time, ser_pro_time ]
        test_times[it][1] = [ par_mem_time, par_pro_time ]
        tot_times[it]     = [ ser_time    , par_time     ]



    fig = plt.figure()
    ax  = fig.add_subplot(111)
    ax.set_xlabel('Number of Processes')
    ax.set_ylabel('Time [s]')
    ax.xaxis.grid(True)
    ax.yaxis.grid(True)
    lines = []
    names = []

    l1, = ax.plot(proc_range, test_times[:,0,0], linewidth=line_width)
    lines.append(l1)
    names.append('Serial Memory')
    l1, = ax.plot(proc_range, test_times[:,0,1], linewidth=line_width)
    lines.append(l1)
    names.append('Serial Processor')
    l1, = ax.plot(proc_range, tot_times[:,0], linewidth=line_width)
    lines.append(l1)
    names.append('Serial')

    l1, = ax.plot(proc_range, test_times[:,1,0], linewidth=line_width)
    lines.append(l1)
    names.append('Parallel Memory')
    l1, = ax.plot(proc_range, test_times[:,1,1], linewidth=line_width)
    lines.append(l1)
    names.append('Parallel Processor')
    l1, = ax.plot(proc_range, tot_times[:,1], linewidth=line_width)
    lines.append(l1)
    names.append('Parallel')

    plt.legend(lines, names, ncol=2, prop={'size':legend_size}, fancybox=True, shadow=True, bbox_to_anchor=(1.10, 1.10))
    fig.savefig(fig_name,dpi=fig.get_dpi())
    print ' - Saved to ', fig_name
    plt.show(block=True)

最佳答案

从上面的讨论中,我认为您已经获得了所需的信息,但我添加了一个答案来收集事实,以防其他人受益(而且我想自己解决)。 (感谢@bamboon,他首先提到了其中的一些内容。)

首先,您的 MacBook 有一个带有四个物理内核的 CPU,但芯片的设计使得每个内核的硬件都能够运行两个线程。这称为“同步多线程”(SMT),在这种情况下体现为英特尔的 hyperthreading特征。所以总的来说你有 8 个“虚拟核心”(4 + 4 = 8)。

请注意,操作系统对所有虚拟内核一视同仁,即它不区分物理内核提供的两个 SMT 线程,这就是为什么 sysctl 在您查询时返回 8 的原因。 Python 会做同样的事情:

>>> import multiprocessing
>>> multiprocessing.cpu_count()
8

其次,您遇到的加速限制是一种众所周知的现象,在这种情况下,并行性能会饱和,并且不会随着处理该问题的更多处理器的增加而提高。此效果由 Amdahl's Law 描述,关于根据可以并行化的代码量和串行运行的代码量,期望从多个处理器获得多少加速的定量陈述。

通常有许多因素会限制相对加速,包括操作系统的细节甚至计算机的体系结构(例如 SMT 如何在硬件核心中工作),因此即使您尽可能多地并行化代码,您的性能不会无限扩大。了解串行瓶颈在哪里可能需要对您的程序及其运行环境进行非常详细的分析。

您可以在 this question 中找到一个很好的讨论示例.

希望对您有所帮助。

关于python - 为什么我的并行性能达到顶峰?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15856300/

相关文章:

performance - 为什么这个 Monte Carlo Haskell 程序这么慢?

c - 多线程中的死锁

ruby-on-rails - 如何测试涉及异步/并行操作的 Ruby on Rails 方法?

python - Flask-Restful 可以接受不区分大小写的参数名称吗?

python - 如何预处理时间序列测试数据以进行分类预测?

java - 如何将 byte[] 转换为 bytebuffer native 内存?

javascript - jQuery : should i check for visibility before hiding an element?

java - 在 JSP 中并行调用的最佳方法是什么?

python - 如何仅选择数据框中每个用户 ID 的最新日期以及按用户 ID 列出的列表?

python - numpy.float128 在 Windows 中不存在,但从 OpenGL 调用