AWS Glue에서 spark를 통해 최대 4억 행의 랜덤 데이터들을 생성하면서, 메모리 부족으로 계속 워커노드가 죽어버리는 문제가 발생한적이 있었다.
이를 해결하기위해 알아보던 중 Generator을 활용할 수 있다는 것을 알게되었고, 이를 활용하고자 했었다.
결과적으로는 다른 방법을 찾아 Generator를 프로젝트에 사용하지는 않았지만, 그래도 정리해두면 요긴하게 쓰일 것 같아 정리해보았다.
Iterable은 반복 가능한 객체를 의미하여, 내부 요소를 하나씩 차례로 반환할 수 있는 객체임
Iterable에는 리스트, 문자열, 딕셔너리, 튜플, 집합과 같은 자료 유형이 해당함
내부에 __iter__ 메서드가 있는 모든 객체라고 볼 수 있고, 재사용이 가능함
Iterator는 Iterable한 객체의 요소를 순차적으로 접근할 수 있는 객체
Iterable 객체에서 __iter__ 메소드를 통해 생성되는 객체.
__next__ 메소드를 가지고 있어 이를 통해 다음 요소를 반환함
상태가 존재하는 객체로 한 번 순회하면 사용할 수 없음
Iterable 객체의 경우 순회를 당하는 객체이고, Iterator 객체는 순회를 주관하는 객체로 볼 수 있음
또한, Iterable 객체는 순회 후 다시 순회할 수 있지만, Iterator는 한 번 순회하면 또 순회할 수 없음.
재순회를 위해서는 새로 Iterator를 선언해주어야함.
예시를 통해 보자
# iterable 객체인 리스트
list1 = [1, 2, 3, 4]
for num in list1:
print(num)
for num in list1:
print(num)
# 결과 값
1
2
3
4
1
2
3
4__iter__ 메소드를 통해 임의의 Iterator를 생성__next__ 메서드를 통해 Iterator의 끝까지 값이 반환됨list1 = [1, 2, 3, 4]
iterator1 = list1.__iter__()
iterator1.__next__()
iterator1.__next__()
iterator1.__next__()
iterator1.__next__()
# iterator1.__next__() # StopIteration 에러를 발생시킴
iterator1 = list1.__iter__()
iterator1.__next__()
iterator1.__next__()
iterator1.__next__()
iterator1.__next__()
# iterator1.__next__() # StopIteration 에러를 발생시킴
# 결과 값
1
2
3
4
1
2
3
4제너레이터는 yield 문을 이용하여 Iterator를 만들어내는 함수
함수 내에서 yield를 사용하면 yield 사용 여부를 떠나서 그 함수의 타입은 gererator가 됨
제너레이터는 yield 구문을 이용한 함수를 선언하거나, 제너레이터 컴프리헨션이라는 것을 통해 생성할 수 있음
제너레이터 컴프리헨션은 리스트의 것과 비슷하게 "(값 반복표현식 필요시 조건문)"를 사용하면 됨
def generator1(iterable):
for i in iterable:
yield i
gen1 = generator1(range(3))
gen2 = (i for i in range(3))
yield는 해당 라인을 실행하고, yield가 있는 함수를 호출한 쪽으로 프로그램의 제어를 넘겨줌
예시를 통해 이해해보자
def generator1():
for i in range(3):
yield i + 1
print(i + 1, "번째 실행")
return 'Done'
gen = generator1()
print(next(gen))
print(next(gen))
print(next(gen))
# 3번 이후 실행시에는 더이상 순회할 수 없으므로 StopIteration 에러가 발생
try:
print(next(gen))
except StopIteration as e:
print(e.value) # 이때 generator1에서의 return 값이 반환됨
실행결과 =======================================================
1 # 첫번째 next()에서 출력
1 번째 실행 # 두번째 next()에서 출력
2 # 두번째 next()에서 출력
2 번째 실행 # 세번째 next()에서 출력
3 # 세번째 next()에서 출력
3 번째 실행 # 네번째 next()시 출력되고, 함수 내 반복문 종료로 StopIteration 에러 발생
Done # 얘외 처리로 인해 출력
한번 return으로 동일하게 작성해서 해보자
def return_func():
for i in range(3):
return i + 1
var = return_func()
print(type(var))
print(next(var))
실행결과 =======================================================
<class 'int'>
Traceback (most recent call last):
File "/Users/junyoung-kim/.../test2.py", line 25, in <module>
print(next(var))
^^^^^^^^^
TypeError: 'int' object is not an iterator
yield 구문을 통해 생성하는 generator는 그럼 왜 사용하는 것일까?
이는 이 generator의 특성을 보면 알 수 있다.
위에서 yield는 호출 될 때마다 값을 하나씩 넘겨주고, 또 호출되면 다음 순서의 값을 넘겨주는 특성을 가지고 있다.
이는 리스트와 같은 순회가능한 자료형을 생성하여 통째로 return하는 것보다 iterable 객체 내부의 값을 하나씩 넘겨주는 것으로 메모리 공간에서 큰 이점을 얻을 수 있다.
from sys import getsizeof
def generator1():
for i in range(1_000_000):
yield i + 1
print(i + 1, "번째 실행")
return 'Done'
def get_list():
return [i for i in range(1_000_000)]
print(getsizeof(generator1()))
print(getsizeof(get_list()))
실행결과 =======================================================
216 # size of Generator
8448728 # size of returning entire iterable object
이처럼 대용량의 데이터를 이용하고자할 때에는 순회 데이터의 값을 하나씩 넘겨주는 generator가 훨씬 메모리면에서 효율적인 것을 알 수 있다.
하지만, 수행 시간 면에서는 generator가 더 느리니, 처리 데이터로 인한 메모리 부족이 일어나지 않는 환경에서는 정말 사용을 해야하는 것인지 한 번 더 생각해 볼 필요가 있겠다.
포스트글 잘 읽었습니다. 더 깊게 생각할게 무엇이 있을지 아래에 더 적어보겠습니다.
1. AWS Glue 환경에서 메모리 부족 문제를 해결하기 위한 다른 접근 방법은 어떤 것이 있을까
2. Iterator와 Generator의 내부 동작 원리를 Python의 메모리 관리 측면에서 깊게 이해해볼 수 있을까(가비지 컬렉션과 같은 개념)
포스팅 잘 읽었습니다! 앞서 스파크에서 랜덤 데이터를 생성하며 메모리 부족으로 워커노드가 자꾸 죽는 현상이 발생했다고 언급해 주셨는데, 그 코드를 어떻게 수정할 수 있는지 궁금해요