파이썬에 대한 기본적인 이해를 위해 작성했습니다
파이썬 최적화에 대해 알아보자.
다음 블로그를 참고했습니다.
https://this-programmer.tistory.com/508
#8 Int interning
주의할 점은, Immutable 객체를 참조할 때 동일한 객체를 참조하지 않을 수도 있다.
Python이 자동으로 공유 참조를 하지만, 언제나 작동하지는 않으며 될 때도 있고 안될 때도 있다는 점을 명심해야 한다.
지난 포스팅 '변수와 메모리 #2'의 마지막 줄이다.
a = 10
b = 10
위의 경우 동일한 메모리 주소를 참조하게 된다.
하지만, 257 이상의 값을 참조하는 경우 Python은 공유 참조를 만들지 않는다.
a = 500
b = 500
변수 a와 b는 서로 다른 메모리 주소를 참조하게 된다.
무슨 일이 일어나는 걸까?
Interning이라는 재사용 객체에 대해서 알아야 한다.
CPython(C언어로 구현된 파이썬이며, java나 c#으로 만들어진 파이썬이 존재)이 작동과 동시에 [-5, 256] 범위의 정수를 미리 프리로드(cache)한다. 즉, 해당 범위의 정수를 참조하거나 사용할 때마다 Python은 해당 객체의 캐시 된 버전을 사용한다는 것이다.
새 객체를 생성하지 않고 목록에서 찾아보고 이미 있는 것을 사용한다.
-5부터 256까지의 정수는 단일 객체(Singleton objects)이다.
왜 interning은 최적화 전략인 걸까?
작은 수의 정수는 자주 사용되기 때문에 해당 객체의 메모리를 매 순간 할당하고 반환하는 것보다 효율적이기 때문이다.
a = 10
위의 코드를 읽을 때 파이썬은 10의 값을 가지는 int 객체를 생성하는 것이 아니라, 이미 존재하는 객체를 가리킨다는 것이 맞다.
#9 String interning
정수 객체가 재사용되는 것처럼 문자열 객체도 자동으로 재사용되지만, 모든 문자열에 적용되는 것은 아니다.
식별자는 재사용된다.
식별자란 변수 이름, 함수 이름, 클래스 이름 등을 뜻하며 '_' 혹은 '문자'로 시작해야 하며 오직 '숫자', '문자', '_'으로만 구성된다는 규칙이 있다.
a = 'my_str_value'
b = 'my_str_value'
a와 b는 같은 메모리 주소를 참조하게 된다.
인터닝을 활용하면, a == b라는 식을 사용해야 할 때 연산 속도가 훨씬 더 빠른 a is b를 사용할 수 있다는 것을 알 수 있다.
( == 은 메모리에 저장된 객체의 상태를 검사해야 하지만, is는 같은 참조하는 주소값만 검사하면 되기 때문)
식별자 규칙에 벗어난 'my str value'같은 문자열은 파이썬이 재사용 객체로 만들지 않는다.
하지만, sys.intern() 메서드를 사용하여 문자열 인터닝을 강제할 수 있다.
import sys
a = sys.intern('my str value')
b = sys.intern('my str value')
b를 선언할 때도 sys.intern() 메서드를 사용해야 한다.
sys.intern() 메서드를 언제 사용해야 할까?
반복이 있을 수 있는 많은 수의 문자열을 처리할 수 있다. 대규모 텍스트 말뭉치(NLP) 토큰화
많은 문자열 비교를 하는 것에 사용할 수 있다. 등호 연산자 대신 IS 연산자 사용
다음 코드를 보고, 등호 연산자와 is 연산자의 속도 차이를 확인해 보자.
def compare_using_equals(n):
a = 'a long string that is not interned' * 200
b = 'a long string that is not interned' * 200
for i in range(n):
if a == b:
pass
def compare_using_interning(n):
a = sys.intern('a long string that is not interned' * 200)
b = sys.intern('a long string that is not interned' * 200)
for i in range(n):
if a is b:
pass
import time
start = time.perf_counter()
compare_using_equals(10000000)
end = time.perf_counter()
print('equality: ', end-start) # equality: 2.965451618090112
start = time.perf_counter()
compare_using_interning(10000000)
end = time.perf_counter()
print('identity: ', end-start) # identity: 0.28690104431129626
perf_counter()의 단위는 밀리세컨드다.
#10 Peephole
Peephole 최적화는 컴파일 중에 발생하는 최적화다.
Constant Expressions
- numeric calculation
- short sequences length < 20
def my_func():
a = 24 * 60
b = (1, 2) * 5
c = 'abc' * 3
d = 'ab' * 11
e = 'the quick brown fox' * 10
f = [1, 2] * 5
my_func.__code__.co_consts
'''
(None,
24,
60,
1,
2,
5,
'abc',
3,
'ab',
11,
'the quick brown fox',
10,
1440,
(1, 2),
(1, 2, 1, 2, 1, 2, 1, 2, 1, 2),
'abcabcabc')
'''
위의 예에서 볼 수 있듯이 24 * 60은 미리 계산되어 상수(1440)로 캐시 된다.
마찬가지로 (1, 2) * 5는 (1, 2, 1, 2, 1, 2, 1, 2, 1, 2)로 캐시 되었고 'abc' * 3은 abcabcabc로 캐시 됐다.
반면에 'the quick brown fox' * 10은 미리 계산되지 않았다. 너무 길어서
마찬가지로 [1, 2] * 5는 변경 가능(Mutable)하므로 상수가 아니기 때문에 미리 계산되지 않았다.
Membership Tests
Mutables are replaced by immutables
def my_func():
if e in [1, 2, 3]:
pass
list 객체 [1, 2, 3]은 위 코드에서 상수일까 변수일까?
변할 수 없는 값이기 때문에 파이썬은 해당 코드를 상수로 인식하고 Immutable 인 튜플로 대체한다.
- lists → tuples
- sets → frozensets
일반적으로 코드를 작성할 때 set membership test는 list나 tuple보다 효율적이다.
즉, [1, 2, 3] 혹은 (1, 2, 3)을 사용하는 것보다 {1, 2, 3}으로 작성해야 한다.
다음 코드에서 얼마나 효율적인지 확인할 수 있다.
import string
import time
# string.ascii_letters 는 영어 알파벳 소문자, 대문자 모두 출력(return)하는 메서드
char_list = list(string.ascii_letters)
char_tuple = tuple(string.ascii_letters)
char_set = set(string.ascii_letters)
def membership_test(n, container):
for i in range(n):
if 'p' in container:
pass
start = time.perf_counter()
membership_test(10000000, char_list)
end = time.perf_counter()
print('list membership: ', end-start) # list membership: 2.6035404184015434
start = time.perf_counter()
membership_test(10000000, char_tuple)
end = time.perf_counter()
print('tuple membership: ', end-start) # tuple membership: 2.602491734651276
start = time.perf_counter()
membership_test(10000000, char_set)
end = time.perf_counter()
print('set membership: ', end-start) # set membership: 0.3743007599607324
보시다시피 set 멤버십 테스트는 상당히 빠르게 실행된다. 이는 기본적으로 dictionary와 유사한 객체이기 때문이다. 따라서 멤버십을 결정하기 위해 항목을 조회하는 데 해시 맵이 사용된다.
set, dictionart, hash map, hash table... 이에 대한 학습 자료는 다음의 블로그를 참고하면 도움이 될 것 같다.
파이썬 set의 데이터구조부터 hashtable까지(갑분 C 등장)
파이썬 set는 멤버를 찾을 때 왜 빠를까? 라는 질문에서 시작한 data structure 파헤치기.
velog.io
'TIL (Today I Learned) > Python' 카테고리의 다른 글
[Python] Numeric types (0) | 2023.04.13 |
---|---|
[Python] 변수와 메모리 #3 (0) | 2023.04.05 |
[Python] 변수와 메모리 #2 (0) | 2023.04.03 |
[Python] 변수와 메모리 #1 (0) | 2023.04.03 |
[Python] Classes (0) | 2023.04.02 |
댓글