多个子流程需要很长时间才能完成

Multiple subprocesses take a lot of time to complete

我有一个进程 运行 使用 subprocess 模块的 Popen:

result = subprocess.Popen(['tesseract','mypic.png','myop'])
st = time()
while result.poll() is None:
    sleep(0.001)
en = time()

print('Took :'+str(en-st))

这导致:

Took :0.44703030586242676

这里调用tesseract处理一张图片mypic.png(附后),并将OCR的结果输出到myop.txt.

现在我希望代表this comment (or see this directly)在多个进程上发生这种情况,所以代码在这里:

lst = []
for i in range(4):
    lst.append(subprocess.Popen(['tesseract','mypic.png','myop'+str(i)]))

i=0
l = len(lst)
val = 0 
while(val!=(1<<l)-1):
    if(lst[i].poll() is None):
        print('Waiting for :'+str(i))
        sleep(0.01)
    else:
        temp = val
        val = val or (1<<(i))
        if(val!=temp):
            print('Completed for :'+temp)
    i = (i+1) %l

此代码所做的是对 tesseract 进行 4 次调用,将流程对象保存在列表 lst 中,遍历所有这些对象直到 all 其中完成。底部给出了无限循环实现的说明。

这里的问题是后一个程序要花很多时间才能完成。它使用 poll() 函数不断等待进程完成,即 None 直到进程尚未完成。这不应该发生。它应该只花费 0.44 秒多一点。不像10分钟!为什么会这样?

我通过深入研究 pytesseract 发现了这个特定的错误,这在 运行 并行使用 multiprocessingpathos 时花费了很多时间。所以这是一个更大问题的缩小版。我的问题可以在 .

中找到

无限循环的解释: val 初始为 0。当第 i 个进程完成时,它与 2^i 进行或运算。因此,如果有 3 个进程,那么如果第一个进程 (i=0) 完成,则 2^0 = 1val 进行“或”运算,使其成为 1。第二个和第三个进程完成后,val 变成 2^0 | 2^1 | 2^2 = 7。并且 2^3-1 也是 7。因此循环一直有效,直到 val 等于 2^{number of processes}-1

Per the faq(我强调):

Tesseract 4 also uses up to four CPU threads while processing a page, so it will be faster than Tesseract 3 for a single page.

If your computer has only two CPU cores, then running four threads will slow down things significantly and it would be better to use a single thread or maybe a maximum of two threads! Using a single thread eliminates the computation overhead of multithreading and is also the best solution for processing lots of images by running one Tesseract process per CPU core.

Set the maximum number of threads using the environment variable OMP_THREAD_LIMIT.

To disable multithreading, use OMP_THREAD_LIMIT=1.

因此,如果您希望同时 运行 多个 tesseract 进程,您可能希望减少(或试验)OMP_THREAD_LIMIT。 最佳值取决于您的机器可以同时支持的线程数。

例如,在我的机器上:

import subprocess
import time
import os 

t = time.perf_counter()    
tasks = [('mypic.png', 'myop{}'.format(i)) for i in range(4)]
procs = [subprocess.Popen(['tesseract', infile, outfile], env={'OMP_THREAD_LIMIT':'1'})
         for infile, outfile in tasks]
for proc in procs:
    proc.wait()
print('{} s'.format(time.perf_counter()-t))

在 0.220 秒内完成,而没有 env={'OMP_THREAD_LIMIT':'1'} 的相同代码 通常需要 3.1 - 5.1 秒,运行 秒之间有很大差异。


要使您的代码正常工作,请使用 binary bitwise or operator, | instead of the logical or operator, or:

val = val | (1 << (i))

例如,

import time
import subprocess
lst = []
for i in range(4):
    lst.append(subprocess.Popen(['tesseract', 'mypic.png', 'myop'+str(i)]))

i = 0
l = len(lst)
val = 0
counter = 0
while(val != (1 << l)-1):
    if(lst[i].poll() is None):
        time.sleep(0.001)
    else:
        temp = val
        val = val | (1 << (i))
        if(val != temp):
            print('Completed for : {}'.format(i))
    i = (i+1) % l

    counter += 1
print('{} iterations'.format(counter))

一样打印输出
Completed for : 1
Completed for : 2
Completed for : 3
Completed for : 0
6121 iterations

注意循环仍然迭代了数千次,主要是 while lst[i].poll() returns None, 还因为 i = (i+1) % l 可以多次重新访问相同的值。 如果一次迭代需要 0.001s,那么 6121 次迭代将需要 6.121s。所以 while 循环很复杂而且不是很快。