복붙노트

[PYTHON] multiprocessing.Pool ()은 일반 함수를 사용하는 것보다 느립니다.

PYTHON

multiprocessing.Pool ()은 일반 함수를 사용하는 것보다 느립니다.

(이 질문은 multiprocessing.Pool () 코드를 더 빨리 실행하는 방법에 관한 것입니다. 마침내 해결했으며 최종 솔루션은 게시물의 맨 아래에 있습니다.)

원래 질문 :

파이썬을 사용하여 단어를 다른 단어와 비교하고 가장 유사한 단어 목록을 검색하려고합니다. 이렇게하려면 difflib.get_close_matches 함수를 사용하고 있습니다. 필자는 상대적으로 새롭고 강력한 Windows 7 랩톱 컴퓨터에서 Python 2.6.5를 사용하고 있습니다.

내가 원하는 것은 단어 비교 목록이 매우 길고 여러 번 비교 과정을 반복해야하기 때문에 비교 과정의 속도를 높이는 것입니다. 다중 처리 모듈에 대해 들었을 때, 비교가 작업자 작업으로 분리되어 동시에 실행될 수 있다면 (따라서 더 빠른 속도 대신 기계 전력을 사용함) 비교 작업이 더 빨라진다는 것이 논리적 인 것처럼 보였습니다.

그러나 여러 가지 방법을 시도한 후에도 문서에 표시되고 포럼 게시물에 제안 된 메서드를 사용한 후에도 Pool 메서드는 매우 느리고 모든 목록에서 원래 get_close_matches 함수를 실행하는 것보다 훨씬 느립니다. 일단. Pool ()이 너무 느리고 왜 올바르게 사용하는지 이해하는 데 도움이됩니다. 이 문자열 비교 시나리오를 예제로 사용하는 것이 가장 최근의 예입니다. 왜냐하면 내가 이해할 수 없거나 멀티 프로세싱을 사용하지 못하는 부분을 생각할 수 있기 때문입니다. 다음은 difflib 시나리오의 예제 코드로 일반 메소드와 Pooled 메소드의 시간 차이를 보여줍니다.

from multiprocessing import Pool
import random, time, difflib

# constants
wordlist = ["".join([random.choice([letter for letter in "abcdefghijklmnopqersty"]) for lengthofword in xrange(5)]) for nrofwords in xrange(1000000)]
mainword = "hello"

# comparison function
def findclosematch(subwordlist):
    matches = difflib.get_close_matches(mainword,subwordlist,len(subwordlist),0.7)
    if matches <> []:
        return matches

# pool
print "pool method"
if __name__ == '__main__':
    pool = Pool(processes=3)
    t=time.time()
    result = pool.map_async(findclosematch, wordlist, chunksize=100)
    #do something with result
    for r in result.get():
        pass
    print time.time()-t

# normal
print "normal method"
t=time.time()
# run function
result = findclosematch(wordlist)
# do something with results
for r in result:
    pass
print time.time()-t

발견되는 단어는 "안녕하세요"이며 가까운 성냥을 찾으려는 단어 목록은 5 개의 무작위로 결합 된 문자로 구성된 100 만 개의 긴 목록입니다 (설명 목적으로 만 사용). 나는 3 개의 프로세서 코어와 100 개의 chunkksize를 가진 map 함수를 사용한다. (listitems는 내가 생각하는 worker마다 처리된다.) (나는 또한 1000과 10000의 chunkksize를 시도했지만 실제 차이는 없었다). 두 함수 모두에서 함수를 호출하기 직전에 타이머를 시작하고 결과를 루프 한 직후에 종료합니다. 아래에서 볼 수 있듯이 타이밍 결과는 원래 비 수영장 방법에 유리합니다.

>>> 
pool method
37.1690001488 seconds
normal method
10.5329999924 seconds
>>> 

Pool 메서드는 원래 메서드보다 거의 4 배 느립니다. 여기에 누락 된 것이 있습니까? 아니면 풀링 / 다중 처리가 작동하는 방식에 대한 오해일까요? 여기서 문제의 일부는지도 함수가 없음을 반환 할 수 있다고 생각합니다. 따라서 실제 일치가 결과로 반환되기를 원하지만 함수 에서처럼 작성한 경우에만 결과 목록에 수천 개의 불필요한 항목이 추가됩니다. 내가 이해하는 바로는 그것이지도가 작동하는 방식입니다. 필자는 False가 아닌 결과 만 수집하는 필터와 같은 다른 기능에 대해서도 들었지만 다중 처리 / Pool이 필터 방법을 지원한다고 생각하지 않습니다. 멀티 프로세싱 모듈에서 map / imap 외에 다른 함수가 있나요?이 함수는 제 함수가 반환하는 것만 반환하는 것을 도와 줄 수 있습니까? Apply 함수는 여러 개의 인수를 제공 할 때 더 유용합니다.

내가 시도했지만 시간 개선이없는 imap 함수도 있다는 것을 알고있다. 그 이유는 내가 itertools 모듈에 대해 아주 잘 이해하고있는 것과 같은 이유입니다. 아마 "번개처럼 빠른"것이고, 나는이 함수를 호출하는 것이 사실이지만, 경험 한 것에서 그리고 내가 읽었던 것에서입니다. 왜냐하면 함수를 호출하는 것은 실제로 어떤 계산도하지 않기 때문에 결과를 반복하여 결과를 수집하고 분석 할 때 (해당 함수를 호출하는 데 아무런 요점도 없을 때) 시간이 많이 걸리거나 때로는 더 많은 시간이 걸리기 때문입니다 그냥 똑바로 함수의 정상적인 버전을 사용하여. 그러나 나는 그것이 다른 포스트를위한 것이라고 생각한다.

어쨌든, 누군가가 올바른 방향으로 나를 찌를 수 있는지보고 흥분해서 정말 이것에 대한 도움을 주시면 감사하겠습니다. 필자는이 예제가 작동하도록하는 것보다 일반적으로 멀티 프로세싱을 이해하는 데 더 많은 관심이 있습니다. 그러나 이해를 돕기위한 몇 가지 예제 코드 제안이 유용 할 것입니다.

대답:

경기 침체가 추가 프로세스의 느린 시작 시간과 관련이있는 것처럼 보입니다. 나는 충분히 빨라진 .Pool () 함수를 얻을 수 없었다. 마지막으로 작업 부하 목록을 수동으로 분할하고 .Pool () 대신 여러 개의 .Process ()를 사용하고 Queue에 솔루션을 반환하는 것이 최종 해결책입니다. 그러나 가장 중요한 변화가 difflib 검색 기능이 이미 너무 빨라서 아마도 비교할 단어가 아닌 주요 단어로 작업량을 분할했을 수도 있습니다. 다음은 동시에 5 개의 프로세스를 실행하는 새 코드이며 간단한 코드 (6 초 vs 55 초)를 실행하는 것보다 10 배 빠른 속도입니다. 빠른 퍼지 조회에 매우 유용하며 이미 얼마나 빨리 difflib가 있는지를 파악할 수 있습니다.

from multiprocessing import Process, Queue
import difflib, random, time

def f2(wordlist, mainwordlist, q):
    for mainword in mainwordlist:
        matches = difflib.get_close_matches(mainword,wordlist,len(wordlist),0.7)
        q.put(matches)

if __name__ == '__main__':

    # constants (for 50 input words, find closest match in list of 100 000 comparison words)
    q = Queue()
    wordlist = ["".join([random.choice([letter for letter in "abcdefghijklmnopqersty"]) for lengthofword in xrange(5)]) for nrofwords in xrange(100000)]
    mainword = "hello"
    mainwordlist = [mainword for each in xrange(50)]

    # normal approach
    t = time.time()
    for mainword in mainwordlist:
        matches = difflib.get_close_matches(mainword,wordlist,len(wordlist),0.7)
        q.put(matches)
    print time.time()-t

    # split work into 5 or 10 processes
    processes = 5
    def splitlist(inlist, chunksize):
        return [inlist[x:x+chunksize] for x in xrange(0, len(inlist), chunksize)]
    print len(mainwordlist)/processes
    mainwordlistsplitted = splitlist(mainwordlist, len(mainwordlist)/processes)
    print "list ready"

    t = time.time()
    for submainwordlist in mainwordlistsplitted:
        print "sub"
        p = Process(target=f2, args=(wordlist,submainwordlist,q,))
        p.Daemon = True
        p.start()
    for submainwordlist in mainwordlistsplitted:
        p.join()
    print time.time()-t
    while True:
        print q.get()

해결법

  1. ==============================

    1.가장 좋은 추측은 프로세스 간 통신 (IPC) 오버 헤드입니다. 단일 프로세스 인스턴스에서 단일 프로세스에는 단어 목록이 있습니다. 다양한 다른 프로세스에 위임 할 때 주 프로세스는 목록의 섹션을 다른 프로세스로 지속적으로 셔틀해야합니다.

    가장 좋은 추측은 프로세스 간 통신 (IPC) 오버 헤드입니다. 단일 프로세스 인스턴스에서 단일 프로세스에는 단어 목록이 있습니다. 다양한 다른 프로세스에 위임 할 때 주 프로세스는 목록의 섹션을 다른 프로세스로 지속적으로 셔틀해야합니다.

    따라서 더 나은 접근법은 n 개의 프로세스를 스핀 오프하는 것이고, 각 프로세스는 목록의 1 / n 세그먼트를로드 / 생성하고 목록의 해당 부분에 단어가 있는지 확인해야합니다.

    필자는 파이썬의 다중 처리 라이브러리를 사용하여이를 수행하는 방법을 잘 모릅니다.

  2. ==============================

    2.나는 다른 문제에 대한 풀과 비슷한 것을 경험했다. 나는이 시점에서 실제 원인을 확신하지 못한다 ...

    나는 다른 문제에 대한 풀과 비슷한 것을 경험했다. 나는이 시점에서 실제 원인을 확신하지 못한다 ...

    OP Karim Bahgat의 답변 편집은 저에게 도움이되었던 해결책과 같습니다. Process & Queue 시스템으로 전환 한 후, 머신의 코어 수와 함께 인라인 속도를 볼 수있었습니다.

    여기에 예제가 있습니다.

    def do_something(data):
        return data * 2
    
    def consumer(inQ, outQ):
        while True:
            try:
                # get a new message
                val = inQ.get()
    
                # this is the 'TERM' signal
                if val is None:
                    break;
    
                # unpack the message
                pos = val[0]  # its helpful to pass in/out the pos in the array
                data = val[1]
    
                # process the data
                ret = do_something(data)
    
                # send the response / results
                outQ.put( (pos, ret) )
    
    
            except Exception, e:
                print "error!", e
                break
    
    def process_data(data_list, inQ, outQ):
        # send pos/data to workers
        for i,dat in enumerate(data_list):
            inQ.put( (i,dat) )
    
        # process results
        for i in range(len(data_list)):
            ret = outQ.get()
            pos = ret[0]
            dat = ret[1]
            data_list[pos] = dat
    
    
    def main():
        # initialize things
        n_workers = 4
        inQ = mp.Queue()
        outQ = mp.Queue()
        # instantiate workers
        workers = [mp.Process(target=consumer, args=(inQ,outQ))
                   for i in range(n_workers)]
    
        # start the workers
        for w in workers:
            w.start()
    
        # gather some data
        data_list = [ d for d in range(1000)]
    
        # lets process the data a few times
        for i in range(4):
            process_data(data_list)
    
        # tell all workers, no more data (one msg for each)
        for i in range(n_workers):
            inQ.put(None)
        # join on the workers
        for w in workers:
            w.join()
    
        # print out final results  (i*16)
        for i,dat in enumerate(data_list):
            print i, dat
    
  3. ==============================

    3.이러한 문제는 일반적으로 다음과 같이 요약됩니다.

    이러한 문제는 일반적으로 다음과 같이 요약됩니다.

    물론 멀티 프로세싱과 병렬 처리하면됩니다 .Pool (8)을 사용하면 이론적으로 (실제로는 아니지만) 8 배속을 얻을 수 있습니다.

    그러나 이것이 자유롭지는 않다는 것을 염두에 두십시오 - 다음과 같은 오버 헤드를 희생하면서이 병렬 처리를 얻습니다 :

    본질적으로 Pool ()을 사용할 때 원하는 것은 :

    좀 더 심층적 인 탐구를 위해이 게시물과 링크 된 대화 워크 스루를 통해 Pool.map () (및 친구들)에게 전달되는 큰 데이터가 문제를 일으킬 수 있습니다.

    Raymond Hettinger는 여기서 파이썬의 동시성을 적절히 사용하는 방법에 대해서도 설명합니다.

  4. from https://stackoverflow.com/questions/20727375/multiprocessing-pool-slower-than-just-using-ordinary-functions by cc-by-sa and MIT license