[Python] negative index를 사용하자
일반적으로 random access 가능한 자료 구조에서 뒤에서 k
번째 원소를 가져오는 방법은 다음과 같다.
- 자료구조의 크기를 가져온다.
- 크기를 기반으로 접근할 인덱스를 구한다.
- 원하는 자료를 가져온다.
이를 코드로 표현하면 다음과 같다.
파이썬에서는 이를 보다 간결하게 표현하기 위해 negative indexing을 지원한다. negative indexing은 말 그대로 음수를 이용해서 뒤에서부터 자료에 접근하는 것이다. negative indexing을 이용하면 위의 코드는 아래와 같이 표현된다.
파이썬에서는 뒤에서부터 접근할 때 negative indexing을 사용하는 것이 권장되지만 다른 언어로 코딩을 처음 배운 사람들은 파이썬에서도 길이를 가져와 인덱스를 계산하는 방식을 사용하는 경우가 종종 있다. 이런 사람들은 negative indexing이 단순한 syntactic sugar라고 생각하는 것 같다. 하지만 negative indexing은 syntactic sugar가 아니다.
negative indexing이 syntactic sugar라면 negative indexing을 쓴 것과 같은 바이트 코드가 생성돼야 한다. 하지만 둘은 서로 다른 바이트 코드가 생성된다. 위의 각 코드가 실제로 생성하는 바이트 코드는 다음과 같다.
첫 번째 코드는 global memory에서 len
함수를 찾아 호출하여 그 결과에 k
를 뺀 값을 인덱스로 사용하는 코드를 생성하고, 두 번째 코드는 k
를 음수로 만들어 바로 인덱스로 사용한다. 인덱싱을 통해 값을 가지고 오는 바이트 코드는 BINARY_SUBSCR
인데, 물론 인터프리터가 BINARY_SUBSCR
를 실행할 때 주어진 인덱스가 음수이면 내부적으로 리스트의 길이를 가져와 인덱스를 다시 계산하는 과정을 거친다. 하지만 Global Interpreter Lock(a.k.a. GIL) 때문에 이 둘의 동작은 같지 않다.
파이썬의 GIL은 바이트 코드 단위의 atomic만을 보장한다. LOAD_FAST
같은 몇몇 바이트 코드는 GIL을 릴리즈하지 않지만, CALL_FUNCTION
을 포함한 대부분의 바이트 코드는 실행 후 GIL을 릴리즈 할 수도 있다.
또한, 두 코드는 생성되는 바이트 코드가 다르므로 수행 시간도 다르다. 첫 번째 코드는 global memory에서 len
함수를 가지고 와 호출하는 비용이 더 든다. 게다가 negative indexing을 사용할 때 BINARY_SUBSCR
가 내부적으로 계산한 인덱스와 다르게 첫 번째 코드에서 직접 계산한 인덱스는 단순한 정수가 아니라 정수를 들고 있는 PyObject
이다. 따라서 값을 할당하고 해제하는 비용이 더 들기 때문에 negative indexing을 사용하는 것이 더 빠르다.
댓글
댓글 쓰기