Несколько подпроцессов занимают много времени для завершения - PullRequest
0 голосов
/ 18 декабря 2018

У меня есть один процесс, который запускается с использованием 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 (прилагается) и вывода результата распознавания в myop.txt.

enter image description here

Теперь я хочу, чтобы это происходило на несколькихобрабатывает от имени этот комментарий (или смотрите это непосредственно ), поэтому код здесь:

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

Этот код выполняет 4 вызова tesseract, сохраните объекты процесса в списке lst, переберите все эти объекты, пока все из них не будут завершены.Пояснения по реализации бесконечного цикла приведены внизу.

Проблема здесь в том, что последняя программа занимает чертовски много времени для завершения.Он непрерывно ожидает завершения процессов, используя функцию poll(), которая равна None, пока процесс не будет завершен.Этого не должно было случиться.Это должно было занять чуть более 0,44 с.Не то, чтобы 10 минут!Почему это происходит?

Я пришел к этой конкретной ошибке, покопавшись в pytesseract, который занимал много времени при параллельном запуске с использованием multiprocessing или pathos.Так что это уменьшенная версия гораздо большей проблемы.Мой вопрос по этому вопросу можно найти здесь .


Объяснение для бесконечного цикла: val изначально равно 0.Это ORed с 2^i, когда i-й процесс завершается.Итак, если есть 3 процесса, то, если первый процесс (i = 0) завершен, тогда 2^0 = 1 ИЛИ с val, делающим его 1. После завершения второго и третьего процессов val становится 2^0 |2^1 |2^2 = 7. И 2^3-1 также равно 7. Так что цикл работает до тех пор, пока val не будет равен 2^{number of processes}-1.

1 Ответ

0 голосов
/ 19 декабря 2018

Per faq (с моим акцентом):

Tesseract 4 также использует до четырех потоков ЦП при обработке страницы, поэтому он будет быстрее, чем Tesseract 3 дляодна страница.

Если ваш компьютер имеет только два ядра процессора, то выполнение четырех потоков значительно замедлит работу, и было бы лучше использовать один поток или, возможно, максимум два потока! Использование одного потока устраняет накладные расходы на обработку многопоточности, а также является лучшим решением для обработки большого количества изображений, выполняя один процесс Tesseract на ядро ​​ЦП .

Установите максимальное число потоков с помощьюпеременная окружения OMP_THREAD_LIMIT.

Чтобы отключить многопоточность, используйте OMP_THREAD_LIMIT = 1.

Поэтому, если вы хотите одновременно запустить несколько процессов тессеракта, вы можете уменьшить (или поэкспериментировать с)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 секунды, с большим количеством вариаций между запусками.


Чтобы ваш код работал, используйте двоичный файл или оператор | вместо логический или оператор, 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

Обратите внимание на циклповторяется тысячи раз, в основном, когда lst[i].poll() возвращает None, но также и потому, что i = (i+1) % l может вернуться к одному и тому же значению несколько раз.Если одна итерация занимает 0,001 с, то 6121 итерация займет 6,121 с.Так что цикл while сложный и не очень быстрый.

...