基于 numpy 的计算的低效多处理
Inefficient multiprocessing of numpy-based calculations
我正在尝试在 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 核(尽管上述程序总共只占用 CPU 时间的 50%,因此系统信息可能未考虑超线程)。
当我 运行 时,我看到 top
中的 n_par
个进程以 100% 的速度工作 CPU
如果我用循环和每个索引操作替换 numpy
数组操作,效率会显着提高(n_par = 4
提高到大约 75%)。
您使用的测试函数似乎受内存限制。这意味着您看到的 运行 时间受到计算机将数组从内存拉入缓存的速度的限制。例如,a = a + b
行实际上使用了 3 个数组,a
、b
和一个将替换 a
的新数组。这三个数组每个大约 8MB(1e6 个浮点数 * 每个浮点数 8 个字节)。我相信不同的 i7 有 3MB - 8MB 的共享 L3 缓存,所以你不能一次将所有 3 个阵列都放入缓存中。您的 cpu 添加浮点数的速度比将数组加载到缓存中的速度快,因此大部分时间都花在等待从内存中读取数组上。因为缓存在核心之间共享,所以您不会通过将工作分散到多个核心来看到任何加速。
内存绑定操作通常是 numpy 的一个问题,我知道处理它们的唯一方法是使用 cython 或 numba 之类的东西。
一件可以提高效率的简单事情应该是在可能的情况下进行就地数组操作——因此 add(a,b,a)
将 不会 创建一个新数组,而 a = a + b
会。如果可以将 numpy
数组上的 for 循环重写为向量运算,那也应该更有效率。另一种可能性是使用 numpy.ctypeslib
启用共享内存 numpy 数组(参见:)。
我一直在为数学编程数值方法并遇到同样的问题:我没有看到任何 speed-up 用于所谓的 cpu 有界问题。原来我的问题是达到 CPU 缓存内存限制。
我一直在使用 Intel PCM(Intel® Performance Counter Monitor)来查看 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
- 米=100000
- 顺序执行:15s
- 2 处理器池无缓存内存限制:8s
可以看出没有缓存未命中(所有缓存命中),因此 speed-up 几乎是完美的:15/8。
Memory cache hits 2 pool
达到缓存内存限制的示例:
- n=200000
- 米=5000
- 顺序执行:14s
- 2 处理器池缓存内存限制:14s
在这种情况下,我增加了我们操作的向量的大小(并减少了循环大小,以查看合理的执行时间)。在这种情况下,我们可以看到内存已满,进程总是错过缓存内存。因此没有得到任何加速:15/15。
Memory cache misses 2 pool
观察:将操作分配给变量(aux = 0.01*shared)也使用缓存内存并且可以通过内存绑定问题(不增加任何向量大小)。
我正在尝试在 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 核(尽管上述程序总共只占用 CPU 时间的 50%,因此系统信息可能未考虑超线程)。
当我 运行 时,我看到
top
中的n_par
个进程以 100% 的速度工作 CPU如果我用循环和每个索引操作替换
numpy
数组操作,效率会显着提高(n_par = 4
提高到大约 75%)。
您使用的测试函数似乎受内存限制。这意味着您看到的 运行 时间受到计算机将数组从内存拉入缓存的速度的限制。例如,a = a + b
行实际上使用了 3 个数组,a
、b
和一个将替换 a
的新数组。这三个数组每个大约 8MB(1e6 个浮点数 * 每个浮点数 8 个字节)。我相信不同的 i7 有 3MB - 8MB 的共享 L3 缓存,所以你不能一次将所有 3 个阵列都放入缓存中。您的 cpu 添加浮点数的速度比将数组加载到缓存中的速度快,因此大部分时间都花在等待从内存中读取数组上。因为缓存在核心之间共享,所以您不会通过将工作分散到多个核心来看到任何加速。
内存绑定操作通常是 numpy 的一个问题,我知道处理它们的唯一方法是使用 cython 或 numba 之类的东西。
一件可以提高效率的简单事情应该是在可能的情况下进行就地数组操作——因此 add(a,b,a)
将 不会 创建一个新数组,而 a = a + b
会。如果可以将 numpy
数组上的 for 循环重写为向量运算,那也应该更有效率。另一种可能性是使用 numpy.ctypeslib
启用共享内存 numpy 数组(参见:)。
我一直在为数学编程数值方法并遇到同样的问题:我没有看到任何 speed-up 用于所谓的 cpu 有界问题。原来我的问题是达到 CPU 缓存内存限制。
我一直在使用 Intel PCM(Intel® Performance Counter Monitor)来查看 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
- 米=100000
- 顺序执行:15s
- 2 处理器池无缓存内存限制:8s
可以看出没有缓存未命中(所有缓存命中),因此 speed-up 几乎是完美的:15/8。 Memory cache hits 2 pool
达到缓存内存限制的示例:
- n=200000
- 米=5000
- 顺序执行:14s
- 2 处理器池缓存内存限制:14s
在这种情况下,我增加了我们操作的向量的大小(并减少了循环大小,以查看合理的执行时间)。在这种情况下,我们可以看到内存已满,进程总是错过缓存内存。因此没有得到任何加速:15/15。 Memory cache misses 2 pool
观察:将操作分配给变量(aux = 0.01*shared)也使用缓存内存并且可以通过内存绑定问题(不增加任何向量大小)。