使用 Python 多处理进行通信的 OSX 和 Linux 之间的性能差异
Performance discrepancy between OSX and Linux for communication using Python multiprocessing
我一直在尝试更多地了解 Python 的 multiprocessing
模块,并评估进程间通信的不同技术。我写了一个基准测试来比较 Pipe
、Queue
和 Array
(全部来自 multiprocessing
)对于 t运行sferring numpy
数组的性能进程之间。可以找到完整的基准测试 here。这是 Queue
:
的测试片段
def process_with_queue(input_queue, output_queue):
source = input_queue.get()
dest = source**2
output_queue.put(dest)
def test_with_queue(size):
source = np.random.random(size)
input_queue = Queue()
output_queue = Queue()
p = Process(target=process_with_queue, args=(input_queue, output_queue))
start = timer()
p.start()
input_queue.put(source)
result = output_queue.get()
end = timer()
np.testing.assert_allclose(source**2, result)
return end - start
我 运行 在我的 Linux 笔记本电脑上进行了此测试,得到了以下数组大小为 1000000 的结果:
Using mp.Array: time for 20 iters: total=2.4869s, avg=0.12435s
Using mp.Queue: time for 20 iters: total=0.6583s, avg=0.032915s
Using mp.Pipe: time for 20 iters: total=0.63691s, avg=0.031845s
看到 Array
表现如此糟糕,我有点惊讶,因为它使用共享内存并且可能不需要酸洗,但我认为 numpy
中必须有一些我可以复制的东西控制不了。
但是,我 运行 在 Macbook 上进行了相同的测试(同样针对数组大小 1000000),并得到了以下结果:
Using mp.Array: time for 20 iters: total=1.6917s, avg=0.084587s
Using mp.Queue: time for 20 iters: total=2.3478s, avg=0.11739s
Using mp.Pipe: time for 20 iters: total=8.7709s, avg=0.43855s
真正的时间差异并不令人惊讶,因为不同的系统当然会表现出不同的性能。 如此令人惊讶的是相对时间的差异。
这是什么原因造成的?这对我来说是一个非常令人惊讶的结果。看到 Linux 和 Windows,或 OSX 和 Windows 之间如此明显的差异,我不会感到惊讶,但我假设这些东西在两者之间的行为非常相似OSX 和 Linux.
This question 解决了 Windows 和 OSX 之间的性能差异,这似乎更符合预期。
好吧,当我们谈论 multi-process 和 python 时,会发生这些事情:
- OS 完成所有 multi-tasking 工作
- multi-core并发
的唯一选择
- 重复使用系统资源
osx 和 linux 之间存在巨大差异。 osx 基于 Unix 并以不同于 linux 的方式处理多任务进程。
Unix 安装需要严格的 well-defined 硬件机器,并且只能在特定的 CPU 机器上运行,也许 osx 并不是为了加速 python 进程而设计的。这个原因可能是原因。
有关详细信息,您可以阅读 MultiProcessing 文档。
希望对您有所帮助。
TL;DR:OSX 使用 Array 更快,因为在 Linux
上调用 C 库会使 Array 变慢
使用 multiprocessing
中的 Array
使用 C types Python library 进行 C 调用以设置数组的内存。这在 Linux 上比在 OSX 上花费的时间相对更多。您还可以使用 pypy 在 OSX 上观察到这一点。使用 pypy(以及 GCC 和 LLVM)设置内存比在 OSX(使用 Clang)上使用 python3 需要更长的时间。
TL;DR:Windows 和 OSX 的区别在于 multiprocessing 启动新进程的方式
主要区别在于 multiprocessing
的实现,它在 OSX 下的工作方式与在 Windows 下的工作方式不同。最重要的区别是 multiprocessing
启动新进程的方式。可以通过三种方式完成此操作:使用 spawn
、fork
或 forkserver
。 Windows 下的默认( 且仅支持)方式是 spawn
。 *nix(包括OSX)下的默认方式是fork
。这记录在 multiprocessing
文档的 Contexts and start methods 部分。
导致结果偏差的另一个原因是您进行的迭代次数少。
如果增加迭代次数并计算每个时间单位处理的函数调用数,则三种方法之间的结果相对一致。
进一步分析:看cProfile的函数调用
我删除了你的 timeit
计时器函数并将你的代码包装在 cProfile
分析器中。
我添加了这个包装函数:
def run_test(iters, size, func):
for _ in range(iters):
func(size)
然后我将 main()
中的循环替换为:
for func in [test_with_array, test_with_pipe, test_with_queue]:
print(f"*** Running {func.__name__} ***")
pr = cProfile.Profile()
pr.enable()
run_test(args.iters, args.size, func)
pr.disable()
ps = pstats.Stats(pr, stream=sys.stdout)
ps.strip_dirs().sort_stats('cumtime').print_stats()
分析OSX - Linux与Array
的区别
我看到的是Queue比Pipe快,Pipe又比Array快。无论平台如何 (OSX/Linux/Windows),Queue 比 Pipe 快 2 到 3 倍。在 OSX 和 Windows 上,Pipe 比 Array 快大约 1.2 和 1.5 倍。但是在 Linux 上,Pipe 比 Array 快 3.6 倍左右。换句话说,在 Linux 上,Array 比在 Windows 和 OSX 上慢得多。我是st运行ge.
使用cProfile数据,我比较了OSX和Linux之间的性能比。 sharedctypes.py
中的Array
和RawArray
两个函数调用比较耗时。这些函数仅在 Array 场景中调用(而不是在 Pipe 或 Queue 中)。在 Linux 上,这些调用几乎占了 70% 的时间,而在 OSX 上只占了 42% 的时间。所以这是一个主要因素。
如果我们放大 to the code, we see that Array
(line 84) calls RawArray
, and RawArray
(line 54) does nothing special, except a call to ctypes.memset
(documentation)。所以我们有一个嫌疑人。我们来测试一下。
以下代码使用timeit来测试将1MB内存缓冲区设置为'A'的性能。
import timeit
cmds = """\
import ctypes
s=ctypes.create_string_buffer(1024*1024)
ctypes.memset(ctypes.addressof(s), 65, ctypes.sizeof(s))"""
timeit.timeit(cmds, number=100000)
运行 这在我的 MacBookPro 和我的 Linux 服务器上证实了这个行为在 Linux 上比在 OSX 上运行慢得多。知道pypy is on OSX compiled using GCC and Apples LLVM, this is more akin to the Linux world than Python, which is on OSX compiled directly against Clang。通常,Python 程序在 pypy 上比在 CPython 上运行得更快,但上面的代码在 pypy 上运行速度慢 6.4 倍(在相同的硬件上!)。
我对 C 工具链和 C 库的了解有限,所以我无法深入挖掘。所以我的结论是: OSX 和 Windows 使用 Array 更快,因为对 C 库的内存调用使 Array 在 Linux[=110 上变慢=].
分析 OSX - Windows 性能差异
接下来,我 运行 在 OSX 和 Windows 下的双启动 MacBook Pro 上执行此操作。优点是底层硬件是一样的;只有 OS 不同。我将迭代次数增加到 1000,将大小增加到 10.000。
结果如下:
- OSX:
- 数组:10.895 秒内调用 225668 次
- 管道:6.894 秒内调用 209552 次
- 队列:7.892 秒内有 728173 个调用
- Windows:
- 数组:296.050 秒内调用 354076 次
- 管道:234.996 秒内调用 374229 次
- 队列:250.966 秒内有 903705 个调用
我们可以看到:
- Windows 实现(使用
spawn
)比 OSX(使用 fork
)需要更多调用;
- Windows 实现每次调用比 OSX 花费更多时间。
虽然不是很明显,但需要注意的是,如果您查看每次调用的平均时间,三种多处理方法(数组、队列和管道)之间的相对模式) 相同(见下图)。也就是说:Array,Queue,Pipe在OSX和Windows中的性能差异完全可以用两个因素来解释:1. Python 两个平台之间的性能; 2. 两个平台处理多处理的不同方式。
换句话说:调用次数的差异由 multiprocessing
文档的 Contexts and start methods 部分解释。 OSX 和 Windows 之间 Python 的性能差异解释了执行时间的差异。如果将这两个组件排除在外,Array、Queue 和 Pipe 的相对性能(或多或少)在 OSX 和 Windows 上具有可比性,如下图所示。
我一直在尝试更多地了解 Python 的 multiprocessing
模块,并评估进程间通信的不同技术。我写了一个基准测试来比较 Pipe
、Queue
和 Array
(全部来自 multiprocessing
)对于 t运行sferring numpy
数组的性能进程之间。可以找到完整的基准测试 here。这是 Queue
:
def process_with_queue(input_queue, output_queue):
source = input_queue.get()
dest = source**2
output_queue.put(dest)
def test_with_queue(size):
source = np.random.random(size)
input_queue = Queue()
output_queue = Queue()
p = Process(target=process_with_queue, args=(input_queue, output_queue))
start = timer()
p.start()
input_queue.put(source)
result = output_queue.get()
end = timer()
np.testing.assert_allclose(source**2, result)
return end - start
我 运行 在我的 Linux 笔记本电脑上进行了此测试,得到了以下数组大小为 1000000 的结果:
Using mp.Array: time for 20 iters: total=2.4869s, avg=0.12435s
Using mp.Queue: time for 20 iters: total=0.6583s, avg=0.032915s
Using mp.Pipe: time for 20 iters: total=0.63691s, avg=0.031845s
看到 Array
表现如此糟糕,我有点惊讶,因为它使用共享内存并且可能不需要酸洗,但我认为 numpy
中必须有一些我可以复制的东西控制不了。
但是,我 运行 在 Macbook 上进行了相同的测试(同样针对数组大小 1000000),并得到了以下结果:
Using mp.Array: time for 20 iters: total=1.6917s, avg=0.084587s
Using mp.Queue: time for 20 iters: total=2.3478s, avg=0.11739s
Using mp.Pipe: time for 20 iters: total=8.7709s, avg=0.43855s
真正的时间差异并不令人惊讶,因为不同的系统当然会表现出不同的性能。 如此令人惊讶的是相对时间的差异。
这是什么原因造成的?这对我来说是一个非常令人惊讶的结果。看到 Linux 和 Windows,或 OSX 和 Windows 之间如此明显的差异,我不会感到惊讶,但我假设这些东西在两者之间的行为非常相似OSX 和 Linux.
This question 解决了 Windows 和 OSX 之间的性能差异,这似乎更符合预期。
好吧,当我们谈论 multi-process 和 python 时,会发生这些事情:
- OS 完成所有 multi-tasking 工作
- multi-core并发 的唯一选择
- 重复使用系统资源
osx 和 linux 之间存在巨大差异。 osx 基于 Unix 并以不同于 linux 的方式处理多任务进程。
Unix 安装需要严格的 well-defined 硬件机器,并且只能在特定的 CPU 机器上运行,也许 osx 并不是为了加速 python 进程而设计的。这个原因可能是原因。
有关详细信息,您可以阅读 MultiProcessing 文档。
希望对您有所帮助。
TL;DR:OSX 使用 Array 更快,因为在 Linux
上调用 C 库会使 Array 变慢使用 multiprocessing
中的 Array
使用 C types Python library 进行 C 调用以设置数组的内存。这在 Linux 上比在 OSX 上花费的时间相对更多。您还可以使用 pypy 在 OSX 上观察到这一点。使用 pypy(以及 GCC 和 LLVM)设置内存比在 OSX(使用 Clang)上使用 python3 需要更长的时间。
TL;DR:Windows 和 OSX 的区别在于 multiprocessing 启动新进程的方式
主要区别在于 multiprocessing
的实现,它在 OSX 下的工作方式与在 Windows 下的工作方式不同。最重要的区别是 multiprocessing
启动新进程的方式。可以通过三种方式完成此操作:使用 spawn
、fork
或 forkserver
。 Windows 下的默认( 且仅支持)方式是 spawn
。 *nix(包括OSX)下的默认方式是fork
。这记录在 multiprocessing
文档的 Contexts and start methods 部分。
导致结果偏差的另一个原因是您进行的迭代次数少。
如果增加迭代次数并计算每个时间单位处理的函数调用数,则三种方法之间的结果相对一致。
进一步分析:看cProfile的函数调用
我删除了你的 timeit
计时器函数并将你的代码包装在 cProfile
分析器中。
我添加了这个包装函数:
def run_test(iters, size, func):
for _ in range(iters):
func(size)
然后我将 main()
中的循环替换为:
for func in [test_with_array, test_with_pipe, test_with_queue]:
print(f"*** Running {func.__name__} ***")
pr = cProfile.Profile()
pr.enable()
run_test(args.iters, args.size, func)
pr.disable()
ps = pstats.Stats(pr, stream=sys.stdout)
ps.strip_dirs().sort_stats('cumtime').print_stats()
分析OSX - Linux与Array
的区别我看到的是Queue比Pipe快,Pipe又比Array快。无论平台如何 (OSX/Linux/Windows),Queue 比 Pipe 快 2 到 3 倍。在 OSX 和 Windows 上,Pipe 比 Array 快大约 1.2 和 1.5 倍。但是在 Linux 上,Pipe 比 Array 快 3.6 倍左右。换句话说,在 Linux 上,Array 比在 Windows 和 OSX 上慢得多。我是st运行ge.
使用cProfile数据,我比较了OSX和Linux之间的性能比。 sharedctypes.py
中的Array
和RawArray
两个函数调用比较耗时。这些函数仅在 Array 场景中调用(而不是在 Pipe 或 Queue 中)。在 Linux 上,这些调用几乎占了 70% 的时间,而在 OSX 上只占了 42% 的时间。所以这是一个主要因素。
如果我们放大 to the code, we see that Array
(line 84) calls RawArray
, and RawArray
(line 54) does nothing special, except a call to ctypes.memset
(documentation)。所以我们有一个嫌疑人。我们来测试一下。
以下代码使用timeit来测试将1MB内存缓冲区设置为'A'的性能。
import timeit
cmds = """\
import ctypes
s=ctypes.create_string_buffer(1024*1024)
ctypes.memset(ctypes.addressof(s), 65, ctypes.sizeof(s))"""
timeit.timeit(cmds, number=100000)
运行 这在我的 MacBookPro 和我的 Linux 服务器上证实了这个行为在 Linux 上比在 OSX 上运行慢得多。知道pypy is on OSX compiled using GCC and Apples LLVM, this is more akin to the Linux world than Python, which is on OSX compiled directly against Clang。通常,Python 程序在 pypy 上比在 CPython 上运行得更快,但上面的代码在 pypy 上运行速度慢 6.4 倍(在相同的硬件上!)。
我对 C 工具链和 C 库的了解有限,所以我无法深入挖掘。所以我的结论是: OSX 和 Windows 使用 Array 更快,因为对 C 库的内存调用使 Array 在 Linux[=110 上变慢=].
分析 OSX - Windows 性能差异
接下来,我 运行 在 OSX 和 Windows 下的双启动 MacBook Pro 上执行此操作。优点是底层硬件是一样的;只有 OS 不同。我将迭代次数增加到 1000,将大小增加到 10.000。
结果如下:
- OSX:
- 数组:10.895 秒内调用 225668 次
- 管道:6.894 秒内调用 209552 次
- 队列:7.892 秒内有 728173 个调用
- Windows:
- 数组:296.050 秒内调用 354076 次
- 管道:234.996 秒内调用 374229 次
- 队列:250.966 秒内有 903705 个调用
我们可以看到:
- Windows 实现(使用
spawn
)比 OSX(使用fork
)需要更多调用; - Windows 实现每次调用比 OSX 花费更多时间。
虽然不是很明显,但需要注意的是,如果您查看每次调用的平均时间,三种多处理方法(数组、队列和管道)之间的相对模式) 相同(见下图)。也就是说:Array,Queue,Pipe在OSX和Windows中的性能差异完全可以用两个因素来解释:1. Python 两个平台之间的性能; 2. 两个平台处理多处理的不同方式。
换句话说:调用次数的差异由 multiprocessing
文档的 Contexts and start methods 部分解释。 OSX 和 Windows 之间 Python 的性能差异解释了执行时间的差异。如果将这两个组件排除在外,Array、Queue 和 Pipe 的相对性能(或多或少)在 OSX 和 Windows 上具有可比性,如下图所示。