【问题标题】:Inefficient multiprocessing of numpy-based calculations基于 numpy 的计算的低效多处理
【发布时间】:2015-03-31 01:02:41
【问题描述】:

我正在尝试在 Python 的 multiprocessing 模块的帮助下并行化一些使用 numpy 的计算。考虑这个简化的例子:

import time
import numpy

from multiprocessing import Pool

def test_func(i):

    a = numpy.random.normal(size=1000000)
    b = numpy.random.normal(size=1000000)

    for i in range(2000):
        a = a + b
        b = a - b
        a = a - b

    return 1

t1 = time.time()
test_func(0)
single_time = time.time() - t1
print("Single time:", single_time)

n_par = 4
pool = Pool()

t1 = time.time()
results_async = [
    pool.apply_async(test_func, [i])
    for i in range(n_par)]
results = [r.get() for r in results_async]
multicore_time = time.time() - t1

print("Multicore time:", multicore_time)
print("Efficiency:", single_time / multicore_time)

当我执行它时,multicore_time 大致等于single_time * n_par,而我希望它接近single_time。事实上,如果我只用time.sleep(10) 替换numpy 计算,这就是我得到的——完美的效率。但由于某种原因,它不适用于numpy。这可以解决吗,还是numpy的一些内部限制?

一些可能有用的附加信息:

  • 我使用的是 OSX 10.9.5、Python 3.4.2,CPU 是 Core i7,具有(根据系统信息报告)4 个内核(尽管上述程序总共只占用了 50% 的 CPU 时间,因此系统信息可能未考虑超线程)。

  • 当我运行它时,我看到 top 中的 n_par 进程以 100% 的 CPU 运行

  • 如果我用循环和按索引操作替换 numpy 数组操作,效率会显着提高(n_par = 4 提高到大约 75%)。

【问题讨论】:

  • 你在做什么操作?在我的机器上,一些numpy 操作会在多核上自动执行。
  • 主要是数组操作(没有linalg 的东西)。特别是,我不认为上面示例中的操作是并行的(至少当我运行test_func 的单个实例时,我看不到它与top)。
  • 基于thisthisnumpy 的 CPU 关联设置似乎干扰了multiprocessing。让我知道这是否能解决问题。如果是这样,那么恐怕这应该被标记为重复。
  • @senderle:是的,我已经看到了这些问题。不幸的是,答案要么使用taskset(在OSX 中不可用),要么设置一个MKL 特定的环境变量(如果你不使用MKL,则不适用)。
  • P.S.另请参阅附加信息 #3:仅导入 numpy 并不会造成太大影响。

标签: python numpy multiprocessing


【解决方案1】:

看起来您正在使用的测试函数是内存绑定的。这意味着您看到的运行时间受到计算机将数组从内存拉入缓存的速度的限制。例如,a = a + b 行实际上使用了 3 个数组,ab 和一个将替换 a 的新数组。这三个数组每个大约 8MB(1e6 个浮点数 * 每个浮点数 8 个字节)。我相信不同的 i7 具有类似 3MB - 8MB 的共享 L3 缓存,因此您无法一次将所有 3 个阵列放入缓存中。您的 cpu 添加浮点数的速度比将数组加载到缓存中的速度更快,因此大部分时间都花在等待从内存中读取数组上。由于缓存是在内核之间共享的,因此将工作分散到多个内核上并不会带来任何加速。

内存绑定操作通常是 numpy 的一个问题,我知道处理它们的唯一方法是使用 cython 或 numba 之类的东西。

【讨论】:

  • 谢谢,这是有道理的。如果我将数组大小减小到 10000(并增加迭代次数),效率会上升到 ~0.8。在我的实际问题中,数组有大约 100 万个元素,如示例所示,但计算稍微复杂一些,效率约为 0.5。
  • "现在处理它们" -> "知道处理它们"
【解决方案2】:

应该提高效率的一件简单的事情应该是在可能的情况下进行就地数组操作——所以add(a,b,a)创建一个新数组,而a = a + b 会。如果您对 numpy 数组的 for 循环可以重写为向量操作,那也应该更有效。另一种可能性是使用numpy.ctypeslib 来启用共享内存numpy 数组(参见:https://stackoverflow.com/a/5550156/2379433)。

【讨论】:

    【解决方案3】:

    我一直在为数学编程数值方法并且遇到同样的问题:对于所谓的 cpu 有界问题,我没有看到任何加速。事实证明我的问题是达到了 CPU 缓存内存限制。

    我一直在使用英特尔 PCM(英特尔® 性能计数器监视器)来查看 cpu 高速缓存的行为(在 Linux ksysguard 中显示它)。我还禁用了 2 个处理器以获得更清晰的结果(2 个处于活动状态)。

    这是我通过这段代码发现的:

    def somethinglong(b):
        n=200000
        m=5000
        shared=np.arange(n)
        for i in np.arange(m):
            0.01*shared
    
    pool = mp.Pool(2)
    jobs = [() for i in range(8)]
    for i in range(5):
        timei = time.time()
        pool.map(somethinglong, jobs , chunksize=1)
        #for job in jobs:
           #somethinglong(job)
    print(time.time()-timei)
    

    未达到缓存内存限制的示例:

    • n=10000
    • m=100000
    • 顺序执行:15s
    • 2 处理器池无缓存内存限制:8s

    可以看出没有缓存未命中(所有缓存命中),因此加速几乎完美:15/8。 Memory cache hits 2 pool

    达到缓存内存限制的例子:

    • n=200000
    • m=5000
    • 顺序执行:14s
    • 2个处理器池缓存内存限制:14s

    在这种情况下,我增加了我们操作的向量的大小(并减小了循环大小,以查看合理的执行时间)。在这种情况下,我们可以看到内存已满,进程总是错过缓存内存。因此没有得到任何加速:15/15。 Memory cache misses 2 pool

    观察:将操作分配给变量 (aux = 0.01*shared) 也使用缓存内存并且可以通过内存限制问题(不增加任何向量大小)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-26
      • 1970-01-01
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 2018-01-13
      相关资源
      最近更新 更多