python - 使用 Python 多处理进行通信时 OSX 和 Linux 之间的性能差异

标签 python linux macos multiprocessing communication

我一直在尝试更多地了解 Python 的 multiprocessing模块并评估进程间通信的不同技术。我写了一个基准来比较 Pipe 的性能, Queue , 和 Array (全部来自 multiprocessing)用于转移 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 之间的性能差异,这似乎更令人期待。

最佳答案

TL;DR:OSX 使用 Array 更快,因为在 Linux 上调用 C 库会减慢 Array

使用 Array来自 multiprocessing使用 C types Python library进行 C 调用以设置数组的内存。这在 Linux 上比在 OSX 上花费的时间相对更多。您还可以使用 pypy 在 OSX 上观察这一点。使用 pypy(以及 GCC 和 LLVM)设置内存比在 OSX 上使用 python3(使用 Clang)花费的时间要长得多。

TL;DR:Windows 和 OSX 的区别在于多处理启动新进程的方式

主要区别在于 multiprocessing 的实现,它在 OSX 和 Windows 下的工作方式不同。最主要的区别是方式multiprocessing开始一个新的进程。有三种方法可以做到这一点:使用 spawn , forkforkserver . Windows 下默认(且仅支持)的方式是 spawn . *nix(包括OSX)下的默认方式是fork .这记录在 Contexts and start methodsmultiprocessing的部分文档。

结果偏差的另一个原因是您采用的迭代次数较少。

如果增加迭代次数并计算每个时间单位处理的函数调用次数,则可以在三种方法之间获得相对一致的结果。

进一步分析:用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 的区别

我看到的是队列比管道快,管道比数组快。无论平台(OSX/Linux/Windows)如何,Queue 都比 Pipe 快 2 到 3 倍。在 OSX 和 Windows 上,Pipe 大约比 Array 快 1.2 和 1.5 倍。但在 Linux 上,Pipe 大约比 Array 快 3.6 倍。换句话说,在 Linux 上,Array 比在 Windows 和 OSX 上慢得多。这很奇怪。

使用 cProfile 数据,我比较了 OSX 和 Linux 之间的性能比。有两个函数调用需要很多时间:ArrayRawArraysharedctypes.py .这些函数仅在 Array 场景中调用(不在 Pipe 或 Queue 中)。在 Linux 上,这些调用占用了近 70% 的时间,而在 OSX 上只占用了 42% 的时间。所以这是一个主要因素。

如果我们放大 to the code ,我们看到 Array (第 84 行)调用 RawArray , 和 RawArray (第 54 行)没有什么特别的,除了调用 ctypes.memset ( documentation )。所以我们有一个嫌疑人。让我们测试一下。

以下代码使用 timeit 来测试将 1 MB 内存缓冲区设置为“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在 OSX 上使用 GCC 编译和苹果 LLVM ,这比 Python 更类似于 Linux 世界,Python 在 OSX 上直接针对 Clang 编译。 .通常,Python 程序在 pypy 上的运行速度比在 CPython 上快,但上面的代码在 pypy 上的运行速度要慢 6.4 倍(在相同的硬件上!)。

我对 C 工具链和 C 库的了解有限,所以我无法深入挖掘。所以我的结论是: OSX 和 Windows 使用 Array 更快,因为在 Linux 上对 C 库的内存调用减慢了 Array .

OSX-Windows性能差异分析

接下来,我在 OSX 和 Windows 下的双启动 MacBook Pro 上运行它。优点是底层硬件相同;只有操作系统不同。我将迭代次数增加到 1000,大小增加到 10.000。

结果如下:
  • 操作系统:
  • 数组:225668 次调用在 10.895 秒内
  • 管道:6.894 秒内 209552 次调用
  • 队列:728173 个调用在 7.892 秒内
  • 视窗:
  • 数组:296.050 秒内 354076 次调用
  • 管道:234.996 秒内 374229 次调用
  • 队列:250.966 秒内有 903705 个调用

  • 我们可以看到:
  • Windows 实现(使用 spawn)比 OSX(使用 fork)需要更多的调用;
  • Windows 实现每次调用比 OSX 花费更多的时间。

  • 不是很明显,但需要注意的是,如果您查看每次调用的平均时间,三种多处理方法(数组、队列和管道)之间的相对模式是相同的(见下图)。换句话说: OSX 和 Windows 中 Array、Queue 和 Pipe 的性能差异完全可以用两个因素来解释: 1. 两个平台之间 Python 性能的差异; 2. 两个平台处理多处理的不同方式。

    换句话说:调用次数的差异由 Contexts and start methods 解释multiprocessing的部分文档。 OSX 和 Windows 之间 Python 的性能差异解释了执行时间的差异。如果去掉这两个组件,数组、队列和管道的相对性能在 OSX 和 Windows 上(或多或少)相当,如下图所示。

    Performance differences of Array, Queue and Pipe between OSX and Windows

    关于python - 使用 Python 多处理进行通信时 OSX 和 Linux 之间的性能差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47894191/

    相关文章:

    python - 如何将 Django/Python 应用程序暴露给网络?

    你能帮我修改这段代码,让它在旧版本的 linux 中编译吗

    java - 使用 hadoop 运行一个 jar 示例文件

    python - 在 pip 和 pip3 中安装 psycopg2 时出现安装错误

    macos - NSTask 从沙盒应用程序中生成

    windows - Windows、Linux、MacOS 中的图形应用程序

    python - SimpleCV:打开 Display() 时回溯错误

    python - 获取 Dataframe Pandas 中最高值的列和行索引

    python - Jython 动态导入或重新加载

    linux - 如何摆脱期望: spawn id exp7 not open