是什么限制了我在分叉进程中的 CPU 使用率?

是什么限制了我在分叉进程中的 CPU 使用率?

我正在多核 Linux 机器上运行一些长时间运行的程序(出于科学目的)。这些进程由一个小守护进程控制,当一个进程完成时,它会重新启动下一个作业(我一次运行 3-6 个) - 但我注意到它们并不总是使用 100% CPU。程序(和守护进程)是用 Python 编写的。

当我在 Mac OS X 上运行此代码时,我可以运行程序数周,并且它们将始终使用尽可能多的可用系统资源,同时机器在正常温度下运行。

我刚刚开始尝试在 Debian Linux(另一台机器上)上运行这样的东西,有 6 个核心和比工作需要更多的 RAM。我同时运行 5 个这样的工作。

几天前,当我第一次开始工作时,我有 5 个 Python 进程top,每个进程都使用 100% CPU。大约一天后,我检查了它的运行情况,发现有 3 个进程的 CPU 利用率为 100%,另外两个进程的 CPU 利用率为 50%。最近(大约 4 天)我有 5 个进程都以 20% CPU 运行。

可能是什么原因造成的?似乎没有任何迹象表明 Debian Wheezy 上预装了 CPU 使用管理工具,而且我自己(据我所知)没有安装类似的东西,也没有配置它。另外,由于限制似乎根据恶魔运行的时间而变化,所以我不相信它可能是这样的系统。我检查了机器是否过热,它似乎并不比它所在的(冷)房间热多少;风扇/通风口吹出的空气畅通无阻且凉爽。

这些进程仍在运行,因此我可以测量可能对调试有用的任何内容(运行时间长度、进程优先级等),以便调试此问题。谁能告诉我从哪里开始,或者可能的解决方案是什么?

更新:

当我用 3 个线程而不是 5 个线程尝试同样的事情时,每个线程的利用率下降到 33%(在最初下降到 50% 之后)。

有没有什么程序或者调度策略限制单个进程的所有子进程总数为100%?因为这似乎就是正在发生的事情。

下一个测试是直接在单独的 shell 中运行脚本screen(顺便说一句,第一个脚本是从内部启动的screen),看看是否有任何速度减慢。像这样手动划分作业是一种不错的解决方法,但相当令人恼火(并且应该是不必要的。)当然,一般来说,此类问题可能无法通过这种方式解决,但由于每个作业的所有结果都是保存到磁盘而不是返回到线程管理器,我会逃脱它。

更新2:

从不同屏幕实例启动的单独进程在 14 小时后仍以 100% CPU 运行,如果我看到任何减速,将会报告,但正如预期的那样,这种情况不受任何限制的影响。

有人愿意写(或指点我)一些解释 Linux 上进程优先级的东西吗?我想知道我的生成进程是否被标记为较低优先级(因为它本身使用很少的 CPU),然后子进程继承它。

编辑:

有人问我正在运行的脚本以及分叉守护进程的功能。

长时间运行的脚本是一个大型计算,它总是以 100% CPU 运行直到完成,并且对于并行化或多处理没有任何有趣的作用。 (这是一个经过广泛测试的断言。)进一步澄清 - 我在 Mac 上看到这些进程以低于 100% CPU 运行的唯一一次是在过热或分页/交换时。这些都与 Linux 情况无关。

这是分叉出来的函数,然后管理长时间运行的进程:

from multiprocessing import Process
import time, sys, os

# An alternative entry point which runs N jobs in parallel over a list of files.
# Note, since this is designed to be used as a library function, we "return" from the initial
# function call rather than exiting.
def run_over_file_list(script_path, list_of_data_files, num_processes, timeout=float('inf')):
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            return
    except OSError, e:
        print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

    # decouple from parent environment
    os.chdir("/")
    os.setsid()
    os.umask(0)

    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent, print eventual PID before
            print "Daemon PID %d" % pid
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
        sys.exit(1)

    # OK, we're inside a manager daemon.
    if os.path.isfile(status_filename):
        raise Exception("a daemon is already running. failed.")

    f = open(status_filename, "w")
    f.write(str(os.getpid()))
    f.close()

    jobs = [script_path] * num_processes
    data_files_remaining = [f for f in list_of_data_files]
    update_files_remaining_file(len(data_files_remaining))

    assert num_processes <= len(data_files_remaining)

    restart = False

    with nostdout():
        while True:
            processes = []

            for job in jobs:
                p = Process(target=file_list_worker, args=(job, data_files_remaining.pop(0)))
                p.started = time.time()
                p.start()
                processes.append(p)

            stop = False
            while True:
                time.sleep(10)

                ended = []
                for i, p in enumerate(processes):
                    if not p.is_alive():
                        j = i
                        ended.append((j,p))
                    elif time.time() - p.started > timeout:
                        p.terminate()
                        j = i
                        ended.append((j,p))
                if not stop:
                    for tup in ended:
                        if not data_files_remaining:
                            stop = True
                            break
                        i, e = tup
                        new_p = Process(target=file_list_worker, args=(jobs[i], data_files_remaining.pop(0)))
                        new_p.started = time.time()
                        new_p.start()
                        processes[i] = new_p
                        # old e will be garbage collected
                else:
                    if len(ended) == len(processes) and not data_files_remaining:
                        stop = False
                        break

                try:
                    command = check_for_command()
                    if command == "stop":
                        stop = True
                    elif command == "restart":
                        stop = True
                        restart = True
                    elif command == "kill":
                        for p in processes:
                            p.terminate()
                        clear_command()
                        os.remove(status_filename)
                        exit(0)
                except NoCommandError:
                    pass

                update_files_remaining_file(len(data_files_remaining))

            clear_command()

            update_files_remaining_file(len(data_files_remaining))

            if not restart:
                os.remove(status_filename)
                break
            else:
                jobs = None
                restart = False

    # While in a fork, we should never return (will continue running the original/calling script in parallel, hilarity ensues.)
    exit(0)

编辑2:

优先事项

20因此,无论来源如何,一切似乎都优先运行;预限制进程、后限制进程、守护进程管理器、直接从屏幕下的 shell 运行的进程。

ulimit -a

来自bash:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 127788
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 127788
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

来自鱼:

Maximum size of core files created                           (kB, -c) 0
Maximum size of a process’s data segment                     (kB, -d) unlimited
Maximum size of files created by the shell                   (kB, -f) unlimited
Maximum size that may be locked into memory                  (kB, -l) 64
Maximum resident set size                                    (kB, -m) unlimited
Maximum number of open file descriptors                          (-n) 1024
Maximum stack size                                           (kB, -s) 8192
Maximum amount of cpu time in seconds                   (seconds, -t) unlimited
Maximum number of processes available to a single user           (-u) 127788
Maximum amount of virtual memory available to the shell      (kB, -v) unlimited

来自屏幕下的鱼:

(与普通鱼完全一样。)


很久以后更新

我还注意到从单独的 shell 运行长时间运行的进程的这个错误。例如:

Instance 1: 17% (one core of 6 at 100%.)
Instance 2: 8% (one core of 6 at  50%.)
Instance 3: 8% (one core of 6 at  50%.)

如果我将实例 2 的优先级更改为“非常高”,则状态变为:

Instance 1: 17% (one core of 6 at 100%.)
Instance 2: 17% (one core of 6 at 100%.)
Instance 3: 0% (one core of 6 at    0%.)

如果优先级再次相等,我们将返回到第一个状态。

我开始认为这个问题可能与特定的硬件配置或其他东西有关,但我缺乏进一步调试的工具/知识。

相关内容