python의 병렬 프로필

그럼 병렬 실행 코드는 무슨 뜻입니까?응, 표면 단계의 정의는 병렬 실행 코드일 뿐이야.그러나 어떤 상황에서는 항상 그렇지 않다는 것을 곧 알게 될 것이다.또 다른 문제는 왜 우리는 동시에 코드를 실행해야 합니까?우선 코드의 성능을 향상시키고 소프트웨어 작업을 더욱 원활하게 할 수 있다.
본고에서, 나는python 코드에 병발성을 추가하여 성능을 향상시키는 방법을 보여 드리지만, 우선,python 코드가 어떻게 정상적으로 작동하는지 알아보겠습니다.

소개하다.


python 해석기는 위에서 아래로 코드를 실행합니다. 이것은 모든 코드가 그 위의 코드에 의존하여 실행되기 전에 실행된다는 것을 의미합니다.그러나 항상 그렇지는 않습니다. 다음 코드 세션을 예로 들겠습니다.
def do_something():
    time.sleep(2)
    print(10)

do_something()
print(20)
위 코드에서 do_something 함수는 우리가 10을 인쇄하기 전에 호출되었다. 이 함수는 2초 동안 휴면되어 있어서 해석기를 비우게 하고, 20을 얻기 전에 10을 인쇄한다.
따라서 해석기가 한가할 때 우리는 20을 인쇄하거나 다른 일을 하고 싶은 장면을 상상해 보자.해결 방안을 만듭니다 (914).

실을 꿰다

threads란?위키백과에 따르면'실행 루틴은 스케줄러가 독립적으로 관리할 수 있는 최소 프로그래밍 명령 서열로 스케줄러는 통상적으로 운영체제의 일부분이다'.와, 그건 입만 열면 내 지식에 따라 그다지 전문적이지 않은 정의를 내릴 수 있게 해줘.루틴은 하나의 코드 블록으로 서로 독립적으로 운행할 수 있고 한 번에 하나씩 실행할 수 있다.python에서는 한 번에 하나의 스레드 접근 해석기만 있을 수 있습니다. 전역 해석기 자물쇠 (GIL) 가 있기 때문에 스레드가 thread 에서 실행되는 것을 제한합니다. 우리는 뒤에서 상세하게 토론하지만, GIL here 에 대한 내용을 더 많이 읽을 수 있습니다.그러면 라인은 어떻게 작동합니까?병발을 들었을 때 가장 먼저 떠오르는 것은 동시 또는 병행 운행 코드이다. 그러나 라인은 그렇지 않다. 이것은 단지 병행 운행의 착각일 뿐이다.루틴은 메모리에 저장되어 있으며, 해석기가 비어 있을 때만 실행됩니다. 예를 들어 서버에 요청을 보내고 응답을 기다리거나, 파일을 열고 흐름을 기다리거나, 기다려야 하는 IO 작업은 해석기가 비어 있는 좋은 예입니다.다음 그림은 동기화나 일반python 코드의 작업 원리를 설명한다.

위의 그림을 살펴보면 우리의 두 번째 입출력 작업은 이전 입출력 작업에 의존하고, 첫 번째 작업은 실행하기 전에 2초를 기다려야 완성되고, 작업이 끝나기 전에 2초를 더 사용한 것을 알 수 있다.전체 조작은 약 4초가 걸린다. 이것은 결코 좋지 않다. 사용 라인이 어떻게 이 문제를 해결하는지 보자.

내가 말한 바와 같이 라인은 병행 운행이 아니라 다른 라인이 한가할 때 해석기가 다른 라인을 실행한다. 그러면 우리는 약간의 대기 시간을 절약할 수 있다. 보시다시피 우리는 성능을 향상시켰고 우리는 2.5초의 운행 시간을 얻었다.

실정은 실천 중이다.


이것은 듣기에는 괜찮지만, 우리가 실제 라인을 사용하기 위해 코드를 좀 작성합시다.우선, 나는 예시 코드의 행위를 이해하기 위해 동기화 코드를 작성할 것이다.
import time

def do_something(sec):
    print("Sleeping for %d seconds..."%sec)
    time.sleep(sec)
    print("Done Sleeping for %d seconds"%sec)

start = time.time()
do_something(1)
do_something(2)
do_something(3)
do_something(4)
do_something(5)

print("Finished in %s seconds"%(time.time() - start))
출력
Sleeping for 1 seconds...
Done Sleeping for 1 seconds
Sleeping for 2 seconds...
Done Sleeping for 2 seconds
Sleeping for 3 seconds...
Done Sleeping for 3 seconds
Sleeping for 4 seconds...
Done Sleeping for 4 seconds
Sleeping for 5 seconds...
Done Sleeping for 5 seconds
Finished in 15.0119268894 seconds
sec 매개 변수를 받은 다음 수면 시간을 인쇄한 다음 sec 값에 Done sleep을 출력하는 함수를 만들었습니다.그리고 우리는 1에서 5 (1에서 5 포함) 의 매개 변수로 함수를 호출한다.코드가 정지되기 전에, 우리의 함수는 1, 2, 3, 4, 5초 동안 모두 15초를 휴면합니다.출력을 관찰하면 함수 출력이 다른 것이기 때문에 1은 2 이전에 실행되고 3 이전에 실행되는 것을 발견할 수 있다.
루틴 최적화 코드를 어떻게 사용하는지 봅시다.
import threading
import time

def do_something(sec):
    print("Sleeping for %d seconds..."%sec)
    time.sleep(sec)
    print("Done Sleeping for %d seconds"%sec)

start = time.time()
t1 = threading.Thread(target=do_something, args=(1,))
t2 = threading.Thread(target=do_something, args=(2,))
t3 = threading.Thread(target=do_something, args=(3,))
t4 = threading.Thread(target=do_something, args=(4,))
t5 = threading.Thread(target=do_something, args=(5,))

t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
print("Finished in %s seconds"%(time.time() - start))
출력
Sleeping for 5 seconds...
Sleeping for 4 seconds...
Sleeping for 3 seconds...
Sleeping for 2 seconds...
Sleeping for 1 seconds...
Finished in 0.00273704528809 seconds
Done Sleeping for 1 seconds
Done Sleeping for 2 seconds
Done Sleeping for 3 seconds
Done Sleeping for 4 seconds
Done Sleeping for 5 seconds
나는 루틴 코드가 더 무거워 보인다고 생각하지만, 실행 시간을 절약할 뿐만 아니라, 순환을 통해 작성된 코드의 양도 줄일 수 있다고 보장합니다.위의 코드를 살펴보겠습니다. 이번에는python에서 라인을 실행하는 데 필요한 모든 내용을 포함하는 라인 모듈을 가져왔습니다.우리의 함수는 여전히 같지만, 이번에는 parallel 대상을 만들고 목표 함수에 전송합니다. Thread 자동 호출 함수의 목표 매개 변수에 전달할 때,parathesis를 사용하지 않기 때문에, 함수 대상만 전송합니다. target=do_something 가 아니라 target=do_something().그렇다면 우리는 어떻게 매개 변수를 목표 함수에 전달합니까?루틴 대상은 하나의 매개 변수args를 받아들여 위치 매개 변수를 전달하고 키워드 매개 변수kwargs를 받아들인다.우리는 args를 사용하여 sec값을 전달합니다. 예를 들어 args=(2,). 우리의 예시에서,args는 모든iterable를 매개 변수로 합니다. 예를 들어list,tuple,range e.t.c. 우리는 kwargs 매개 변수를 사용할 수 있습니다. 사전을 매개 변수로 사용할 수도 있습니다. 따라서 우리의 예시에서, 이것은 이렇게 보입니다. kwargs={"sec":2} 종류에 대해 알아야 할 주요 매개 변수입니다.우리는 start 방법으로 모든 라인을 시작합니다. 모든 라인이 순서대로 시작하는 것을 보았기 때문입니다. 그러나 모든 함수 호출이 정지되기 전에finish가 실행될 때 주의하십시오.이것은 의도적인 것입니다. 모든 스레드가 start () 방법으로 만들어진 후에 해석기는 멈추지 않습니다. 이 스레드는 계속 실행되고, 나머지 동기화 코드를 실행합니다. 그리고 한 스레드가 실행될 때, 잠든 후에 코드를 실행합니다. 'Done sleeping for n seconds' 를 인쇄합니다.우리는 어떻게 해석기가 어떤 코드를 실행하기 전에 라인이 완성되기를 기다리게 합니까?알겠습니다. 해결 방안은 Thread 방법으로 해석기를 막아서 우리의 라인이 실행될 때까지 우리의 코드는 다음과 같습니다.
import threading
import time

def do_something(sec):
    print("Sleeping for %d seconds..."%sec)
    time.sleep(sec)
    print("Done Sleeping for %d seconds"%sec)

start = time.time()
t1 = threading.Thread(target=do_something, args=(1,))
t2 = threading.Thread(target=do_something, args=(2,))
t3 = threading.Thread(target=do_something, args=(3,))
t4 = threading.Thread(target=do_something, args=(4,))
t5 = threading.Thread(target=do_something, args=(5,))

t1.start()
t2.start()
t3.start()
t4.start()
t5.start()

t1.join()
t2.join()
t3.join()
t4.join()
t5.join()
print("Finished in %s seconds"%(time.time() - start))
출력
Sleeping for 1 seconds...
Sleeping for 2 seconds...
Sleeping for 3 seconds...
Sleeping for 4 seconds...
Sleeping for 5 seconds...
Done Sleeping for 1 seconds
Done Sleeping for 2 seconds
Done Sleeping for 3 seconds
Done Sleeping for 4 seconds
Done Sleeping for 5 seconds
Finished in 5.00275301933 seconds
보시다시피 모든 라인이 완성되면 현재 출력이 실행되는 총 시간을 출력합니다.따라서, join 방법은 호출된 라인이 끝날 때까지 해석기가 어떤 조작도 하지 못하게 합니다.너는 이 코드를 시험해 보고, 다른 시작 호출과 가입 호출을 재배치해서, 무슨 일이 일어날지 볼 수 있다.그것은 또한 모든 일이 어떻게 작동하는지 직관적으로 이해하는 데 도움을 줄 것이다.우리는 이미 라인을 소개했으니, 지금 우리는 다른 방법, 즉 다처리를 시도할 것이다.

다처리


따라서 코드를 병행으로 실행할 수는 없지만, 다중 처리는 이를 할 수 있습니다.멀티 프로세서를 사용하면 CPU를 충분히 이용할 수 있다.현대 CPU에는 여러 개의 핵이 있는데 전형적인 현대 PC에는 평균 4개의 핵이 있지만python GIL 때문에 우리는 모든 핵을 동시에 사용할 수 없기 때문에 우리는 모든 조작에 하나의 핵만 사용한다.다중 처리는 모든 핵심에서 동시에 실행되는 프로세스를 만들어서 모든 핵심을 이용할 수 있게 한다. 이것도 병행적이다.CPU 제한 작업을 수행할 때 멀티태스킹이 가장 뛰어나기 때문에 이 예에서는 가상 join 함수를 사용하지 않고 주어진 범위 내에서 모든 완벽한 정사각형을 찾을 수 있는 함수를 만들 것입니다. 따라서 코드는 다음과 같습니다.

import time
import math

def find_perfect_squares(n):
    print(f"Finding perfect squares for values from 1 to {n}")
    res = []
    for i in range(1, n+1):
        if math.sqrt(i) in range(1, n+1):
            res.append(i)
    print(f"Found {len(res)} values in range of {n}")
    return res


start = time.time()
find_perfect_squares(1000)
find_perfect_squares(2000)
find_perfect_squares(5000)
find_perfect_squares(3000)
find_perfect_squares(100)
finished = time.time() - start 

print("Finished in %s seconds"%(finished))
출력
Finding perfect squares for values from 1 to 1000
Found 31 values in range of 1000
Finding perfect squares for values from 1 to 2000
Found 44 values in range of 2000
Finding perfect squares for values from 1 to 5000
Found 70 values in range of 5000
Finding perfect squares for values from 1 to 3000
Found 54 values in range of 3000
Finding perfect squares for values from 1 to 100
Found 10 values in range of 100
Finished in 8.968952894210815 seconds
만약 네가 관찰한다면, 너는 우리가 있는 이곳의 함수 계산성이 매우 강하고, 시간 복잡도do_something가 가장 좋은 것이 아니라는 것을 알게 될 것이다.함수 호출의 총 계산 시간은 약 9초이다.일부 계산량이 비교적 적은 호출, 예를 들어 범위 100은 다른 계산량이 비교적 큰 호출을 기다려야 한다. 예를 들어 범위 5000은 각자의 프로세스에서 모든 호출을 실행하여 성능을 향상시킬 수 있다.python 멀티프로세서 API는 루틴과 유사하기 때문에 이 두 가지 API를 배우는 것은 문제가 되지 않습니다. 멀티프로세서를 어떻게 사용해서 최적화시키는지 봅시다.
import multiprocessing
import time
import math

def find_perfect_squares(n):
    print(f"Finding perfect squares for values from 1 to {n}")
    res = []
    for i in range(1, n+1):
        if math.sqrt(i) in range(1, n+1):
            res.append(i)
    print(f"Found {len(res)} values in range of {n}")
    return res


if __name__ == '__main__':

    start = time.time()
    args = [1000, 2000, 5000, 3000, 100]
    processes = []

    for val in args:
        p = multiprocessing.Process(target=find_perfect_squares, args=(val, ))
        processes.append(p)

    for p in processes:
        p.start()

    for p in processes:
        p.join()
    finished = time.time() - start 

    print("Finished in %s seconds"%(finished))
출력
Finding perfect squares for values from 1 to 100
Found 10 values in range of 100
Finding perfect squares for values from 1 to 1000
Found 31 values in range of 1000
Finding perfect squares for values from 1 to 2000
Found 44 values in range of 2000
Finding perfect squares for values from 1 to 3000
Found 54 values in range of 3000
Finding perfect squares for values from 1 to 5000
Found 70 values in range of 5000
Finished in 3.448301315307617 seconds
다중 처리를 사용하려면, 라인을 가져오는 것처럼 다중 처리 모듈을 가져옵니다.그래서 이번에는 for 순환을 사용하여 코드를 만들고 시작하고 가입하는 절차를 통해 코드를 정리하려고 합니다.나는 또한 모든 프로세스 대상을 목록에 저장할 것이다.우리는 과정이 끝날 때까지 이전과 같은 연결 방법을 사용하여 해석기를 막았다.또한 출력을 주의하면 다른 출력이 여기 입력과 다르다는 것을 알 수 있습니다. 첫 번째 출력은 출력되고, 두 번째 출력은 최소한의 출력이 가장 느릴 때까지 출력됩니다.

Make sure you create a process inside the if __name__ == "__main__" block. See the docs for more details.


python에서 프로세스를 만드는 더 간단한 방법이 있습니다. Pool 클래스를 사용하여 프로세스 탱크를 만들고 이렇게 비추는 것입니다.
from multiprocessing import Pool
#snippet
if __name__ == "__main__":
    #snippet
    p = Pool(processes=5)
    p.map(find_perfect_squares, [1000, 2000, 5000, 3000, 100])
맵 방법의 반환 값을 가져와서 프로세스에서 함수가 호출한 반환 값을 얻을 수 있습니다. 맵 방법은 우리가 반환한 값의 목록입니다.
#snippet
pool = p.map(find_perfect_squares, [1000, 2000, 5000, 3000, 100])
print(pool)
출력
[[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961], [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 1]...]
크기 때문에, 이것은 편집된 출력이고, 되돌아오는 값의 인덱스는 입력한 인덱스와 같기 때문에, 풀[0]은 O(n^2) 되돌아오는 값으로 추정된다.

언제


앞에서 말한 바와 같이, 라인은 IO 연결에 있어서 양호하다. 예를 들어 서버에 요청, 파일 처리, 네트워크 작업 등이다.그것은 계산 작업이나 CPU가 제한된 작업을 잘 처리하지 못하며, 때로는 이런 작업에서 가장 나쁘게 실행되기도 한다.다중 처리는 입출력 작업에 사용할 수 있지만, 프로세스마다 메모리 범위가 다르기 때문에 다른 프로세스에 접근할 수 없다는 단점도 있다.그래서 그것은 우리가 그것을 이용하여 무엇을 하는지를 제한한다. 이것이 바로 우리가 그것을 주로 사용해서 계산하는 이유이다.그리고 대부분의 경우, 코드를 최적화할 필요가 없으면, 코드를 사용할 필요가 없다.그러나 무엇을 사용하느냐는 문제의 유형에 달려 있다.

추가 리소스 읽기

  • https://realpython.com/python-concurrency/
  • https://docs.python.org/3/library/concurrent.futures.html
  • https://en.wikipedia.org/wiki/Concurrency_(computer_science)
  • 좋은 웹페이지 즐겨찾기