python3는 메모리 사용을 줄이기 위해 교체 생성기를 사용합니다

기술적 배경


python 인코딩에서 for 순환 처리 작업을 할 때, 메모리에 모든 대기량을 불러옵니다.사실 이것은 필요없다. 왜냐하면 이런 참량은 일회용으로 사용할 가능성이 높기 때문이다. 심지어 많은 장면에서 이런 참량은 메모리에 동시에 저장할 필요가 없기 때문에 이때 본고에서 소개한 교체 생성기 yield를 사용한다.

기본 사용


우선 우리는 하나의 예로 교체 생성기 yield의 기본적인 사용 방법을 보여 드리겠습니다. 이 예의 역할은 하나의 함수를 구성하여 제곱수 그룹을 만드는 데 사용하는 것입니다.일반적인 장면에서 우리는 일반적으로 빈 목록을 직접 구성한 다음에 모든 계산 결과를 목록에 채운다. 마지막으로return 목록을 작성하면 된다. 대응하는 것은 이곳의 함수 square_number.또 다른 함수 square_number_yield는 yield를 보여주기 위해 구성된 함수로 문법을 사용하면return과 같고 매번 하나의 값만 되돌려줍니다.

# test_yield.py

def square_number(length):
    s = []
    for i in range(length):
        s.append(i ** 2)
    return s

def square_number_yield(length):
    for i in range(length):
        yield i ** 2

if __name__ == '__main__':
    length = 10
    sn1 = square_number(length)
    sn2 = square_number_yield(length)
    for i in range(length):
        print (sn1[i], '\t', end='')
        print (next(sn2))

main 함수에서 우리는 두 가지 방법으로 실행한 결과를 비교하여 같은 줄에 인쇄하고 end='지령으로 줄 끝의 줄 바꾸기 기호를 대체할 수 있으며 구체적인 실행 결과는 다음과 같다.

[dechin@dechin-manjaro yield]$ python3 test_yield.py 
0       0
1       1
4       4
9       9
16      16
25      25
36      36
49      49
64      64
81      81

두 가지 방법으로 인쇄된 결과가 같다는 것을 볼 수 있다.아마도 일부 장면에서는 지속적인 저장 함수에서 되돌아오는 결과가 필요할 것이다. 이 점은 yield로도 실현할 수 있다. 다음과 같은 예시를 참고할 수 있다.

# test_yield.py

def square_number(length):
    s = []
    for i in range(length):
        s.append(i ** 2)
    return s

def square_number_yield(length):
    for i in range(length):
        yield i ** 2

if __name__ == '__main__':
    length = 10
    sn1 = square_number(length)
    sn2 = square_number_yield(length)
    sn3 = list(square_number_yield(length))
    for i in range(length):
        print (sn1[i], '\t', end='')
        print (next(sn2), '\t', end='')
        print (sn3[i])

여기서 사용하는 방법은 yield가 생성한 대상을list 형식으로 직접 바꾸거나 sn3 = [i for i in square_number_yield(length)]로 쓰는 것도 가능하며 성능상 차이가 크지 않을 것입니다.위 코드의 실행 결과는 다음과 같습니다.

[dechin@dechin-manjaro yield]$ python3 test_yield.py 
0       0       0
1       1       1
4       4       4
9       9       9
16      16      16
25      25      25
36      36      36
49      49      49
64      64      64
81      81      81


진급 테스트


앞에서 언급한 바와 같이yield를 사용하면 프로그램의 메모리 사용을 절약할 수 있다. 여기서 우리는 100000 크기의 무작위 수조의 제곱과 계산을 테스트한다.정상적인 논리를 사용한다면 다음과 같이 쓰여진 프로그램은 다음과 같다. (python 메모리 사용에 대한 추적 방법은 이 블로그를 참고할 수 있다.)

# square_sum.py

import tracemalloc
import time
import numpy as np
tracemalloc.start()

start_time = time.time()
ss_list = np.random.randn(100000)
s = 0
for ss in ss_list:
    s += ss ** 2
end_time = time.time()
print ('Time cost is: {}s'.format(end_time - start_time))

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

for stat in top_stats[:5]:
    print (stat)

이 프로그램은 한편으로time를 통해 실행 시간을 측정하고, 다른 한편으로tracemalloc 추적 프로그램의 메모리 변화를 이용한다.여기는 np를 먼저 씁니다.random.randn () 은 100000개의 랜덤 수를 직접 생성하여 계산에 사용한다. 그러면 자연히 계산하는 과정에서 이러한 생성된 랜덤 수를 저장해야 하기 때문에 이렇게 많은 메모리 공간을 차지한다.만약에 yield의 방법을 사용한다면 매번 계산에 사용되는 랜덤 수만 생기고 이전 장절의 용법에 따라 이 반복적으로 생성된 랜덤 수도 완전한list로 바뀔 수 있다.

# yield_square_sum.py

import tracemalloc
import time
import numpy as np
tracemalloc.start()

start_time = time.time()
def ss_list(length):
    for i in range(length):
        yield np.random.random()

s = 0
ss = ss_list(100000)
for i in range(100000):
    s += next(ss) ** 2
end_time = time.time()
print ('Time cost is: {}s'.format(end_time - start_time))

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

for stat in top_stats[:5]:
    print (stat)

이 두 예제의 실행 결과는 다음과 같이 비교할 수 있다.

[dechin@dechin-manjaro yield]$ python3 square_sum.py 
Time cost is: 0.24723434448242188s
square_sum.py:9: size=781 KiB, count=2, average=391 KiB
square_sum.py:12: size=24 B, count=1, average=24 B
square_sum.py:11: size=24 B, count=1, average=24 B
[dechin@dechin-manjaro yield]$ python3 yield_square_sum.py 
Time cost is: 0.23023390769958496s
yield_square_sum.py:9: size=136 B, count=1, average=136 B
yield_square_sum.py:14: size=112 B, count=1, average=112 B
yield_square_sum.py:11: size=79 B, count=2, average=40 B
yield_square_sum.py:10: size=76 B, count=2, average=38 B
yield_square_sum.py:15: size=28 B, count=1, average=28 B

비교를 통해 우리는 두 가지 방법의 계산 시간은 거의 차이가 많지 않지만 메모리의 점용에 있어 yield는 뚜렷한 장점을 가진다는 것을 발견했다.물론 이 예가 매우 적절하지는 않을지 모르지만 본고는 주로yield의 사용 방법과 응용 장면을 소개한다.

무한장 교체기


참고 링크 1에서 언급한 용법은 무한한 길이의 교체기이다. 예를 들어 순서대로 모든 소수를 되돌려준다. 그러면 이때 우리가 return으로 모든 요소를 되돌려주고 목록에 저장하는 것은 매우 비경제적인 방법이기 때문에 yield를 사용하여 교체하여 생성할 수 있다. 참고 링크 1의 원본 코드는 다음과 같다.

def get_primes(number):
    while True:
        if is_prime(number):
            yield number
        number += 1

그러면 우리는 while True로 간단한 사례를 보여줄 수 있다. 모든 짝수를 되돌려준다.

# yield_iter.py

def yield_range2(i):
    while True:
        yield i
        i += 2

iter = yield_range2(0)
for i in range(10):
    print (next(iter))

여기에서 우리는 길이를 10으로 제한했기 때문에 최종적으로 10개의 짝수로 되돌아갈 것이다.[dechin@dechin-manjaro yield]$ python3 yield_iter.py

요약


본고는python의 교체기인 yield를 소개했는데 사실 yield에 관해서는 간단하게 단일 원소의 return으로 이해할 수 있다.이렇게 하면 yield의 사용 문법을 초보적으로 이해할 수 있을 뿐만 아니라 yield의 장점도 대충 알 수 있다. 즉, 계산 과정에서 매번 하나의 원소의 메모리만 차지하고 대량의 원소를 메모리에 계속 저장할 필요가 없다는 것이다.
이는python3가 교체생성기를 사용하여 메모리 사용을 줄이는 것에 관한 글을 소개합니다. 더 많은python3가 메모리 사용을 줄이는 내용에 관해서는 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

좋은 웹페이지 즐겨찾기