【问题标题】:Operating-System-Level Changes To Speed Up Python's Multiprocessing?操作系统级别的更改以加速 Python 的多处理?
【发布时间】:2015-12-15 00:17:01
【问题描述】:

在我使用的 linux 系统上,调度程序并不是很慷慨地给从 python 的多处理模块产生的子进程提供 cpu 时间。在 4 核机器上使用 4 个子进程时,根据ps,我得到大约 22% 的 CPU。但是,如果子进程是 shell 的子进程,而不是 python 程序,它会上升到接近 100% 的 CPU。但是与手动拆分我的数据并为每个拆分运行单独的 python 程序相比,多处理是一个更好的界面,并且最好同时获得两全其美(代码组织和高 CPU 利用率)。我尝试将进程的 niceness 设置为 -20,但这没有帮助。

我想知道使用某些选项重新编译 linux 内核是否会帮助调度程序为 python 多处理工作人员提供更多的 CPU 时间。也许有相关的配置选项?

我使用的确切版本是:

$ uname -a
Linux <hostname> 3.19.0-39-generic #44~14.04.1-Ubuntu SMP Wed Dec 2 10:00:35 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

如果这可能与我使用多处理的方式有关,它的形式是:

 with Pool(4) as p:
     p.map(function,data)

更新: 这不是一个可重现的问题。这里报告的结果是几天前的结果,我再次运行测试,多处理过程与我希望的一样快。也许这个问题应该被删除,误导人们对multiprocessing的预期表现不好。

【问题讨论】:

  • 你确定是内核的错吗?这个问题似乎与 Python 无法真正并行运行作业更相关。
  • 多进程工作者是他们自己的进程,有自己的 PID。我的理解是,这些过程彼此不同,就像它们来自我的网络浏览器一样。
  • 您可能 [容易] 使您的内存总线饱和(即)您受内存限制,而不是计算限制。也许,你可以估计或基准来确认。查看 /sys/devices/system/cpu/cpu*/cpufreq 并设置为“性能”
  • 我认为您应该编写一个简单的基准测试,其中每个任务都完全独立于其他任务。然后比较使用 multiprocessing 模块运行它们与启动四个 Python 脚本实例。
  • 这就是我在帖子中所做的和描述的:“但是,如果子进程是 shell 的子进程,而不是 python 程序,它会上升到接近 100% CPU ...手动拆分我的数据,并为每个拆分运行单独的 python 程序"。

标签: python subprocess python-multiprocessing


【解决方案1】:

我认为您的基准测试并不像您想象的那样执行独立的任务。您没有显示function 的代码,但我怀疑它会进行一些同步。

我编写了以下基准。如果我使用--fork 或--mp 选项运行脚本,我总是得到400% 的CPU 利用率(在我的四核机器上)和大约18 秒的可比整体执行时间。但是,如果使用 --threads 选项调用,程序有效地按顺序运行,仅实现大约 100% 的 CPU 利用率,并且由于 mentioned 由 dave 的原因需要一分钟才能完成。

import multiprocessing
import os
import random
import sys
import threading


def find_lucky_number(x):
    prng = random.Random()
    prng.seed(x)
    for i in range(100000000):
        prng.random()
    return prng.randint(0, 100)

def with_threading(inputs):
    callback = lambda x : print(find_lucky_number(x))
    threads = [threading.Thread(target=callback, args=(x,)) for x in inputs]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

def with_multiprocessing(inputs):
    with multiprocessing.Pool(len(inputs)) as pool:
        for y in pool.map(find_lucky_number, inputs):
            print(y)

def with_forking(inputs):
    pids = list()
    for x in inputs:
        pid = os.fork()
        if pid == 0:
            print(find_lucky_number(x))
            sys.exit(0)
        else:
            pids.append(pid)
    for pid in pids:
        os.waitpid(pid, 0)

if __name__ == '__main__':
    inputs = [1, 2, 3, 4]
    if sys.argv[1] == '--threads':
        with_threading(inputs)
    if sys.argv[1] == '--mp':
        with_multiprocessing(inputs)
    elif sys.argv[1] == '--fork':
        with_forking(inputs)
    else:
        print("What should I do?", file=sys.stderr)
        sys.exit(1)

【讨论】:

  • 我所做的与您的 with_multiprocessing 函数非常相似:在 with Pool 上下文中启动进程,并且没有同步或共享内存。我报的结果是前几天发生的,我又试了一遍,结果和你发现的比较像。我正在考虑删除这个问题,因为它不是一个可重现的问题。鉴于您提出了一些好的观点,也许版主知道是否值得这样做。
  • @seewalker 我不知道在这种情况下最好的解决方案是什么。我们谁都不能像现在这样删除问题。我不知道标记是否合适,但这是一种选择。另一方面,您可以尝试重新表述您的问题,问“如何让 Python 使用所有 CPU?”。但请注意不要问一些立即作为重复关闭的内容。
  • 我会留下这个问题,部分原因是@5gon12eder 的回答。他的测试程序让它值得。而且,我是一个长期的程序员,但是 python 新手,所以我从中得到了一些东西。但是,我在使用线程或进程进行 perl WRT 拆分时遇到了类似的问题。即使它是不可重现的,问题和答案都是明确的[并且赞成]。我已经看到很多留下的问题,OP 重试并得到不同的结果。你用免责声明编辑了这个问题——你在[法律和道德上]被覆盖了。 IMO,版主有更大的鱼要炸,没有伤害,这里没有犯规。
【解决方案2】:

欢迎使用 CPython 全局解释器锁。您的线程显示为 linux 内核的不同进程(这就是线程在 Linux 中的一般实现方式:每个线程都有自己的进程,因此内核可以调度它们)。

那么,为什么 Linux 不能一次调度多个它们运行(这就是为什么您的 4 核机器平均大约 25% 减去一点开销)? python 解释器在解释每个线程时持有一个锁,从而阻止其他线程运行(因此无法调度它们)。

要解决此问题,您可以:

  1. 使用进程而不是线程(正如您在问题中提到的那样)
  2. 使用没有全局解释器锁的其他 python 解释器。

【讨论】:

  • 总之,我不认为这是正确的答案。文档的第一段说:multiprocessing 是一个使用类似于threading 模块的 API 支持生成进程的包。 multiprocessing 包提供本地和远程并发,通过使用子进程而不是线程来有效地避开全局解释器锁。
  • 是的,我将其标记为正确答案可能为时过早。我遇到的特殊情况可能与 Global Interpreter Lock 相关(感谢 dave!),但感谢您注意到 multiprocessing 与该锁没有紧密联系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
相关资源
最近更新 更多