Skip to content

Instantly share code, notes, and snippets.

@sigridjineth
Last active December 12, 2023 01:00
Show Gist options
  • Save sigridjineth/84f13b0253e0114a6a45b58b020b4433 to your computer and use it in GitHub Desktop.
Save sigridjineth/84f13b0253e0114a6a45b58b020b4433 to your computer and use it in GitHub Desktop.
Joblib vs multiprocessing

• 대용량 데이터를 처리할 때 병렬 프로그래밍은 필수적이다. 파이썬에서는 병렬 연산을 위해 사용하는 default 패키지로 multiprocessing이 있다. 지금까지는 multiprocessing만 사용해봤는데, 최근에 joblib에 대하여 접하게 되어 간단하게 메모하는 겸 공식문서를 정리해보았다. 이번 게시물에서는 주로 Parallel과 Delayed에 대한 설명을 하고자 한다.

pip install joblib
# poetry add joblib

• 일반적인 for loop를 병렬 처리로 전환해보자

• 아래의 예시는 0에서 9까지의 수를 제곱하고 제곱근을 구하는 단순한 for loop이다.

from math import sqrt
[sqrt(i ** 2) for i in range(10)]

• Joblib을 사용하여 병렬 처리로 전환하면 하기와 같다. 여기에서 n_jobs=2라는 이야기는 두 개의 CPU 코어를 사용하여 작업을 병렬로 처리하겠다는 의미이다. 만약 n_jobs가 -1이라면 모든 코어를 사용하겠다는 뜻이 된다.

from math import sqrt
from joblib import Parallel, delayed
Parallel(n_jobs=2)(delayed(sqrt)(i ** 2) for i in range(10))

• joblib.Parallel은 기본값으로 loky라는 파이썬 모듈을 사용한다. 해당 모듈은 파이썬의 워커들이 동시에 서로 다른 CPU를 사용하도록 만들어주어 결과적으로 멀티프로세싱이 가능하도록 한다. 파이썬의 표준 모듈인 multiprocessing과는 달리 메인 프로그램으로부터 독립적인 워커 프로세스를 생성시키는데, 따라서 부모 프로세스와 별도의 상태가 공유되지 않은 상태에서 각 작업을 독립적으로 실행할 수 있도록 도와준다. • Joblib은 부모 프로그램과 완전히 독립적인 워커 프로세스를 생성시키므로 주어진 입력 데이터를 직렬화하고 메모리 재할당 등의 과정을 거쳐야하므로 큰 입력 데이터의 경우에는 오버헤드가 커질 수 있다. 따라서 Joblib은 큰 배열을 파일 시스템에 자동으로 덤프하고 워커 프로세스가 이를 메모리 맵으로 열 수 있도록 하는 numpy의 memmap을 사용한다. 이를 통해 모든 워커 프로세스가 데이터 세그먼트를 공유할 수 있도록 최적화 전략을 시도한다. • 만약 호출하고자 하는 함수가 GIL (하나의 쓰레드만이 파이썬 오브젝트에 접근할 수 있도록 하는 뮤텍스) 을 해체하는 코드로 이루어져있다면 멀티 쓰레드를 사용하는 것이 훨씬 경제적인데, 예를 들어 Cython의 with nogil의 구문을 이용하면 GIL을 해제할 수 있다.

>>> Parallel(n_jobs=2, prefer="threads")( # threads라는 백엔드를 사용한다
...     delayed(sqrt)(i ** 2) for i in range(10))
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

• 위의 코드 스니펫에서 우리가 delayed라고 하는 키워드를 붙여주는 이유는 무엇일까. 바로 function 단위의 호출을 할 때 Tuple을 반환할 수 있도록 하는 간단한 트릭과 같기 때문이다.

Parallel(n_jobs=8)(getHog(i) for i in allImages)

• 위의 코드는 Parallel 인스턴스를 8개의 워커로 동작하도록 만드는데 • 그 과정에서 getHog(i) for i in allImages라고 하는 리스트를 생성하고 • 생성한 리스트를 Parallel 인스턴스의 인자로 넘기게 된다 • 그런데 문제는 위에서 리스트를 생성하는 과정에서 8개의 워커를 사용하지 않기 때문에 delayed라는 함수와 거기에 들어갈 파라미터를 튜플로 생성해서 Parallel 인스턴스에 할당할 수 있도록 한다.

Parallel(n_jobs=8)(delayed(getHog)(i) for i in allImages)
  1. Parallel 인스턴스를 8개의 worker로 동작하게 만든다.

  2. delayed를 통해 [(getHog, [img1], {}), (getHog, [img2], {}), … ] 이라는 튜플 리스트를 생성한다.

예를 들어 foo(2, g=3)이라는 함수가 있을 때 delayed(foo)(2, g=3)은 (foo, [2], {g: 3})이라는 튜플을 생성한다. 3. Parallel의 튜플 리스트를 전달하고, 8개의 worker로 병렬 처리를 실행한다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment