ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬에서 스레드와 프로세스 소개
    PYTHON 2017. 9. 21. 22:39
    반응형

    원문: https://medium.com/@bfortuner/python-multithreading-vs-multiprocessing-73072ce5600b

    병렬 프로그래밍 초보자 가이드

    Kaggle의 Understanding the Amazon from Space경쟁에서 경쟁하면서 필자는 속도를 향상시킬 수 있는지 확인하기 위해 코드의 여러 부분을 타이밍 내기 시작했습니다. 속도는 Kaggle에서 매우 중요합니다. 자주 순위를 매기려면 수백 가지의 아키텍처 및 하이퍼 파라미터 조합을 시도해야합니다. 1 분간 지속되는 신기원에서 10 초 면도하는 것이 큰 승리입니다.
    놀랍게도 데이터 증가가 가장 큰 병목이었습니다. 회전, 뒤집기, 줌 및 작물과 같은 방법은 Numpy에 의존하여 CPU에서 실행되었습니다. Numpy는 어떤 경우에는 병렬 처리를 사용하고 Pytorch의 데이터 로더도 사용합니다. 하지만 한 번에 3 ~ 5 번 실험을하고 있었고 각 실험마다 고유 한 기능을 수행하고있었습니다. 이것은 비효율적인 것처럼 보였습니다. 병렬 처리로 처리 속도를 향상시킬 수 있는지 궁금합니다.

    병렬 처리란 무엇입니까?

    기본적으로 서로 다른 CPU에서 동시에 코드를 실행하거나 동일한 CPU에서 코드를 실행하고 프로그램이 외부 리소스 (파일로드, API 호출)를 기다리는 동안  비되는" CPU 사이클을 활용하여 속도 향상을 달성하는 두 가지 작업을 동시에 수행합니다. .
    예를 들어, 여기에 정상적인" 프로그램이 있습니다. 단일 스레드를 사용하여 한 번에 하나씩 URL 목록을 다운로드합니다.

    다음은 2 개의 스레드를 사용하는 동일한 프로그램입니다. 스레드간에 URL을 나눠서 우리에게 거의 2 배의 속도 향상을 줍니다.

    이 차트를 생성하는 방법과 그 의미를 알고 싶다면 여기에서 코드를 찾을 수 있지만 간단히 요약하면:
    1. 1. 함수 안에 타이머를 추가하고 시작 및 종료 시간을 반환하십시오.
    URLS = [url1, url2, url3, ...]
    def download(url, base):
        start = time.time() - base
        resp = urlopen(url)
        stop = time.time() - base
        return start,stop
    2. 단일 스레드를 시각화하려면 함수를 여러 번 실행하고 시작 및 중지 시간을 저장하십시오.
    results = [download(url, 1) for url in URLS]
    3. [시작, 정지] 시간의 배열을 바꿔 막대 차트를 그립니다.
    def visualize_runtimes(results):
        start,stop = np.array(results).T
        plt.barh(range(len(start)), stop-start, left=start)
        plt.grid(axis=’x’)
        plt.ylabel("Tasks")
        plt.xlabel("Seconds")
    여러 스레드에 대한 차트를 같은 방법으로 생성 할 수 있습니다. Python 동시성 라이브러리의 메소드는 결과 배열을 반환합니다.

    프로세스 대 스레드

    프로세스는 프로그램의 인스턴스입니다 (예 : Jupyter 노트북, Python 인터프리터). 프로세스는 스레드(하위 프로세스)를 통해 키 입력 읽기, HTML 페이지로드, 파일 저장과 같은 하위 작업을 처리합니다. 스레드는 프로세스 내부에서 실행되며 동일한 메모리 공간을 공유합니다.
    Example: Microsoft Word
    Word를 열면 프로세스가 생성됩니다. 입력을 시작하면 프로세스가 스레드를 생성합니다. 하나는 키 스트로크 읽기, 다른 하나는 텍스트 표시, 하나는 파일 자동 저장, 다른 하나는 맞춤법 오류를 강조합니다. 여러 스레드를 생성하여 Microsoft는 유휴 CPU 시간 (키 입력 또는 파일로드 대기)을 활용하여 생산성을 향상시킵니다.

    프로세스

    • 프로그램을 실행하기 위해 운영 체제에서 생성
    • 프로세스는 여러 스레드를 가질 수 있습니다.
    • 두 프로세스는 동일한 파이썬 프로그램에서 동시에 코드를 실행할 수 있습니다.
    • 열기 및 닫기 프로세스에 시간이 더 걸리기 때문에 프로세스는 스레드보다 오버 헤드가 많습니다.
    • 프로세스간에 메모리 공간을 공유하지 않으므로 프로세스간에 정보를 공유하는 것이 스레드 간 공유보다 느립니다. 파이썬에서 그들은 IO 시간을 필요로하는 배열 같은 데이터 구조를 pickling하여 정보를 공유합니다.

    스레드

    • 스레드는 프로세스 내부에있는 미니 프로세스와 같습니다.
    • 그들은 메모리 공간을 공유하고 효율적으로 동일한 변수를 읽고 씁니다.
    • 두 스레드는 동일한 파이썬 프로그램에서 코드를 동시에 실행할 수 없습니다 (해결 방법 *이 있음에도 불구하고).

    CPU vs Core

    CPU 또는 프로세서는 컴퓨터의 기본적인 컴퓨터 작업을 관리합니다. CPU에는 하나 이상의 Core가 있어 CPU가 동시에 코드를 실행할 수 있습니다.
    단일 코어를 사용하면 CPU 집약적 인 작업(예 : 루프, 산술)의 속도가 향상되지 않습니다. OS는 한 번에 조금씩 각 태스크를 실행하는 태스크간에 앞뒤로 전환합니다. 이러한 이유로 작은 작업(예 : 몇 개의 이미지 다운로드)에서는 멀티 태스킹이 성능에 해를 끼칠 수 있습니다. 여러 작업을 시작하고 유지 관리하는 데 따른 오버 헤드가 있습니다.

    Python’s GIL problem

    CPython(표준 Python 구현)에는 GIL(Global Interpreter Lock)이라는 것이 있는데, 두 프로그램이 같은 프로그램에서 동시에 실행되는 것을 방지합니다. 어떤 사람들은 이것으로 화가 나고, 다른 사람들은 그것을 맹렬히 지키고 있습니다. 그러나 Numpy와 같은 라이브러리는 C로 외부 코드를 실행함으로써이 제한을 우회합니다.

    언제 스레드 대 프로세스를 사용합니까?

    • 프로세스는 다수의 코어를 사용하고 GIL을 피하기 때문에 CPU 집약적 인 Python 연산의 속도를 높입니다.
    • 스레드는 IO 작업 또는 외부 시스템과 관련된 작업에 가장 적합합니다. 스레드는 작업을 보다 효율적으로 결합 할 수 있기 때문입니다. 프로세스는 결과를 결합하기 위해 결과를 pickling해야합니다.
    • 스레드는 GIL 때문에 CPU 집약적인 작업을 위해 Python에서 아무런 이점도 제공하지 않습니다.
    * Dot Product와 같은 특정 작업의 경우 Numpy는 Python의 GIL을 중심으로 작업하고 코드를 병렬로 실행합니다.

    병렬 처리 예제

    파이썬의 concurrent.futures 라이브러리는 놀라울 정도로 즐겁습니다. 기능, 작업 할 항목 목록 및 worker 수를 전달 하기만 하면 됩니다. 다음 몇 섹션에서는 스레드와 프로세스를 언제 사용할지에 대해 자세히 알아보기 위해 실행한 다양한 실험을 수행합니다.
    def multithreading(func, args,
                       workers):
        with ThreadPoolExecutor(workers) as ex:
            res = ex.map(func, args)
        return list(res)
    def multiprocessing(func, args,
                        workers):
        with ProcessPoolExecutor(work) as ex:
            res = ex.map(func, args)
        return list(res)

    API calls

    API 호출에 대해 스레드가 더 잘 작동하고 직렬 처리 및 다중 처리보다 속도가 빨라진다는 사실을 발견했습니다.
    def download(url):
        try:
            resp = urlopen(url)
        except Exception as e:
            print ('ERROR: %s' % e)

    2 threads

    4 threads

    2 processes

    4 processes

    IO Heavy Task

    쓰기 성능이 어떻게 다른지 확인하기 위해 거대한 텍스트 문자열을 전달했습니다. 스레드는 여기에서 이길것 같았지만 다중 처리 또한 런타임을 개선했습니다.
    def io_heavy(text):
        f = open('output.txt', 'wt', encoding='utf-8')
        f.write(text)
        f.close()
    Serial
    %timeit -n 1 [io_heavy(TEXT,1) for i in range(N)]
    >> 1 loop, best of 3: 1.37 s per loop
    4 threads

    4 processes

    CPU Intensive

    멀티 프로세스는 예상대로 여기에서 승리했습니다. 프로세스는 GIL을 피하고 여러 코어에서 동시에 코드를 실행합니다.
    def cpu_heavy(n):
        count = 0
        for i in range(n):
            count += i

    Serial: 4.2 seconds
    4 threads: 6.5 seconds
    4 processes: 1.9 seconds

    Numpy Dot Product

    예상대로, 나는 이 코드에 쓰레드나 프로세스를 추가 해도 도움이 되지 않습니다. Numpy는 뒤에서 외부 C 코드를 실행하여 GIL을 회피합니다.
    def dot_product(i, base):
        start = time.time() - base
        res = np.dot(a,b)
        stop = time.time() - base
        return start,stop
    Serial: 2.8 seconds
    2 threads: 3.4 seconds
    2 processes: 3.3 seconds
    이 실험을 스스로 복제 할 수 있는 노트가 있습니다.

    Resources

    내가 이 주제를 탐구하는 동안 내가 즐겁게 참조한 기사가 있습니다. 특히 시각화에 대한 아이디어를 준 Nathan Grigg의 블로그 게시물을 알려 드리고자합니다.

    반응형

    'PYTHON' 카테고리의 다른 글

    gTTS-token  (0) 2016.07.15
Designed by Tistory.