为什么我的 Python 应用程序停滞了 'system' / 内核 CPU 时间

Why is my Python app stalled with 'system' / kernel CPU time

首先我不确定我是否应该 post 这是一个 Ubuntu 问题或在这里。 但我猜它更像是一个 Python 问题而不是 OS 问题。

我的 Python 应用程序在 64 核 AMD 服务器上的 Ubuntu 之上运行。 它通过 ctypes 调用 .so 从网络上的 5 个 GigE 摄像机中提取图像,然后处理它们。 我发现我的应用程序频繁暂停,导致来自相机的帧被外部相机库丢弃。

为了调试这个,我使用了流行的 psutil Python 包,我用它在一个单独的线程中每 0.2 秒注销一次 CPU 统计数据。 我在该线程中休眠了 0.2 秒,当休眠时间大大延长时,我还看到相机帧被丢弃。 我见过长达 17 秒的停顿! 我的大部分处理要么在 OpenCV 或 Numpy(两者都发布 GIL)中,要么在应用程序的一部分中 multiprocessing.Pool 有 59 个进程(这是为了绕过 Python GIL)。

当暂停发生时,我的调试日志显示 'system'(即内核)CPU 时间在我的许多进程的线程上。

例如。我看到CPU次如下(通常每0.2秒一次)然后突然大跳 ('Process' 数字处于 CPU 利用率,即 1 CPU 完全使用将是 1,Linux top 显示 123% 将是 1.2):

Process user | Process system | OS system % | OS idle %
19.9         | 10.5           | 6           | 74 
5.6          | 2.3            | 4           | 87
6.8          | 1.7            | 11          | 75
4.6          | 5.5            | 43          | 52
0.5          | 26.4           | 4           | 90

我不知道为什么高 OS 系统使用率在匹配高进程系统使用率之前报告了一行。 两者匹配,因为 64 个内核中有 26.4 个 = 41%。那时我的应用程序经历了大约 3.5 秒的暂停 (由我的 CPU 信息记录线程使用 OpenCV 的 cv2.getTickCount() 以及 Python 记录输出中的时间戳跳跃决定) 导致多个相机帧被丢弃。

发生这种情况时,我还记录了我进程的每个线程的 CPU 信息。 对于上面的示例,25 个线程在 'system' CPU 利用率为 0.9 的情况下运行,还有一些线程在 0.6 的情况下运行,这与上述进程的总数 26.4 相匹配。 那时大约有 183 个线程在运行。

这种暂停似乎通常发生在使用多处理池之后(它用于短时间爆发),但绝不是每次使用池时都会发生。 此外,如果我将需要在池外发生的处理量减半,则不会发生相机跳过。

问题:我如何确定为什么 OS 'system' / 内核时间突然耗尽?为什么 Python 应用程序会发生这种情况?

更重要的是:知道为什么会发生这种情况以及如何避免这种情况吗?

备注:

好的。我有自己问题的答案。是的,我花了 3 个多月才走到这一步。

GIL 似乎在 Python 中颠簸,这就是大量 'system' CPU 尖峰和相关停顿的原因。这里有一个good explanation of where the thrashing comes from。该演示文稿也为我指明了正确的方向。

Python 3.2 introduced a new GIL implementation 以避免这种颠簸。结果可以用一个简单的线程示例显示(取自上面的演示):

from threading import Thread
import psutil

def countdown():
    n = 100000000
    while n > 0:
        n -= 1

t1 = Thread(target=countdown)
t2 = Thread(target=countdown)
t1.start(); t2.start()
t1.join(); t2.join()

print(psutil.Process().cpu_times())    

在装有 Python 2.7.9 的 Macbook Pro 上,它使用了 14.7 秒的 'user' CPU 和 13.2 秒的 'system' CPU。

Python 3.4 使用了 15.0s of 'user'(稍微多一点)但是只有 0.2s of 'system'。

所以,GIL 仍然存在,它仍然只运行与单线程代码一样快,但它避免了 Python 2 的所有 GIL 争用,表现为内核('system') CPU 次。我相信,这种争论是导致原始问题的原因。

更新

CPU 问题的另一个原因被发现与 OpenCV/TBB 有关。 .

中有完整记录