News Feed

파이썬 성능 향상, 어렵지만 시도할 가치가 있는 이유

컨텐츠 정보

  • 조회 729

본문

파이썬 사용자의 화를 돋우고 싶다면 이 말만 하면 된다. “파이썬은 느리다.”

많은 유의미한 상황에서 이 말은 사실이다. “순수” 파이썬, 즉 C로 작성된 외부 라이브러리가 없는 파이썬은 C나 C++, 자바, 러스트, 고를 비롯한 다른 언어보다 계산 또는 객체 조작 속도가 휠씬 더 느리다.

파이썬 사용자들은 오랫동안 우회로를 통해 이 문제를 해결해 왔다. 더 빠른 수학이 필요하다면 넘파이(NumPy) 또는 넘바(Numba)와 같은 수학 라이브러리를 사용하거나 사이썬(Cython)을 사용해 코드를 C로 컴파일하면 된다. 이런 외부 해결책으로 작업을 완료할 수 있으며, 파이썬에는 우회로 사용이 하나의 하위 문화로 자리잡을 정도로 발달했다.

그러나 편법에는 항상 비용이 따른다. 넘파이를 사용할 경우 다른 언어로 표현할 수 있는 것보다 더 추상적이고 덜 세분화된 코드를 작성해야 한다는 대가가 있다. 넘바(일부 경우 사이썬도 해당)의 경우 원하는 작업에서 최고 속도를 내기 위해서는 파이썬 언어의 작은 일부만 사용해야 한다.

전반적으로 많은 사용자가 묻는 것은 파이썬을 기본 상태 그대로 더 빠르게 만들 수 있냐는 것이다. 오랜 시간에 걸쳐 도출된 답은 “가능할 수도 있지만 어렵다”이다.

파이썬을 더 빠르게 만들기 어려운 이유

파이썬이 네이티브 기계 코드로 사전에 컴파일되는 언어와 달리 인터프리트 언어라는 점은 파이썬의 성능과는 큰 관계가 없다. 성능을 저해하는 가장 큰 장애물은 파이썬의 가장 밑바탕에 있는 특징인 역동성과 관련된다.

C++나 러스트와 같은 언어에서 어떤 대상에 이름을 지정하면 언어의 동작에 대한 전제를 통해 그 대상의 유형이 무엇인지에 대한 확고한 보장이 제공된다. 컴파일러는 이와 같은 일관성을 활용해서 대상을 처리하는 빠른 코드를 생성할 수 있다.

파이썬에는 이런 보장이 없다. 즉, 명명된 대상은 무엇이든 될 수 있다. 개발자는 그 명명된 것으로 뭔가를 하려고 할 때마다 그것을 찾아보고 무엇이 가능한지를 파악해야 한다. 이로 인해 일반적인 많은 최적화는 애초에 수행할 수 없다.

파이썬 타입 힌트가 도움이 되지 않는 이유

파이썬에 최근 추가된 타입 힌트 시스템도 이 부분에서는 별 도움이 되지 않는다. 타입 힌트는 컴파일 타임 또는 런타임 최적화 툴이 아닌 사전 린팅 툴로만 사용하도록 설계됐기 때문이다. 여기에는 나름의 목적이 있다. 파이썬 타입 힌트의 요점은 개발자가 파이썬의 기본 역동성을 유지하면서 사전에 코드를 린팅할 수 있도록 하는 것이다.

그렇다면 타입 힌트를 사용해서 최적화된 코드를 생성하는 파이썬 변형을 만들 수는 없을까? 그런 것이 실제로 존재한다. 대표적인 예가 사이썬이다. 그런 변형은 순수 기계 타입을 처리할 때만 유의미한 속도 향상을 제공한다. 리스트, 딕셔너리와 같은 파이썬의 객체 기반 타입을 사용하는 순간 이를 관리하기 위해 사이썬의 런타임을 호출해야 하며, 그러면 다시 파이썬의 원래 성능으로 돌아가게 된다.

이 방식 역시 핵심을 빗겨간다. 속도를 높이기 위해 파이썬 밖으로 나가서는 안 된다. 결국 관건은 파이썬 내부에서 무엇을 할 수 있는지다.

지금 파이썬은 어떻게 속도를 높이고 있는가

파이썬 인터프리터인 C파이썬에서는 지난 몇 번의 레퍼런스 버전에 걸쳐 많은 장단기적 변화가 제안되기 시작했다. 이런 제안은 파이썬 개발자들이 계획 중인 내부적인 속도 향상 방법에 대한 힌트를 제공한다.

특수한 적응형 인터프리터는 주어진 코드 영역에서 객체 타입의 상대적인 안정성을 활용하려고 한다. 주어진 작업이 동일한 타입을 사용한다면 이런 작업을 수행하는 일반 바이트코드를 런타임에 타입별 특수 바이트코드로 바꿔 부가적인 조회를 일부 피할 수 있다.

파이썬의 주요 대안 구현 중 하나인 파이파이(PyPy)는 JIT(Just-In-Time) 컴파일러를 사용해 파이썬의 속도를 높인다. 파이파이는 실제 성능 개선으로 이어지는 기계 네이티브 코드를 생성하지만 파이썬과 완전히 별개의 구현이라는 점으로 인한 유지보수 오버헤드와 버그, 호환성 문제 등의 대가가 따른다. 최근 C파이썬용 네이티브 JIT 기술이 구현되기 시작했지만 아직 큰 성능 향상은 이뤄지지 않았으며 향후 개선을 위한 기반을 마련하는 데 의의를 두고 있다.

새로 도입된 또 다른 중요한 변경은 전역 인터프리터 잠금(Global Interpreter Lock, GIL)이 없는 C파이썬 대체 빌드다. 이 메커니즘은 파이썬에서 여러 스레드에 걸쳐 활동을 동기화했지만 그 대가는 파이썬에서 진정한 스레드 병렬 처리를 제공하지는 못한다는 점이다. GIL이 없는 빌드는 멀티스레드 성능 개선을 위한 기회의 문을 열지만 여전히 실험적인 기술로 간주된다.

그 외에도 현재 여러 개념이 구현되고 있으며, 파이프라인에는 더 많은 개념이 개발되고 있다. 여기서 명확히 드러나는 점은 어느 한 가지 방법으로 파이썬의 속도를 마법처럼 빠르게 할 수는 없다는 것이다. 또한 속도를 높이기 위해서는 가능한 모든 상황에서 인터프리터가 하는 일을 줄여야 한다는 것도(예를 들어 타입 검사를 피하거나 참조 계산을 줄임) 알 수 있다.

파이썬 속도 개선, 왜 내부적으로 이뤄져야 하는가?

최근의 최적화 움직임이 일어나기 전에는 파이썬 자체를 더 빠르게 만들거나 새로운 파이썬 런타임(파이파이)을 만들기보다는 파이썬과의 호환성이 높은 새로운 언어를 만들어서 파이썬 사용자를 점진적으로 그 새로운 언어로 전환시키는 편이 낫다는 생각이 보편적이었다.

이런 접근 방식을 주도하는 것은 모조(Mojo) 같은 프로젝트다. 모조는 파이썬과 유사한 구문과 함께 기존 파이썬과 어느 정도의 하위 호환성도 제공하지만 파이썬과 달리 기본적으로 네이티브 기계 코드로 컴파일된다.

그러나 모조는 파이썬을 완전히 대체하는 호환 언어는 아니며, 호환성을 위해 기존의 파이썬을 차용하는 부분에서는 성능 우위를 잃는다. 모조나 비슷한 다른 프로젝트를 불문하고 어느 언어든 파이썬이 갖고 있는 완전한 생태계 호환성과 폭넓은 도입, 그리고 임계점을 달성하기란 힘든 일이다.

이 말은 파이썬 자체가 파이썬의 최선의 대체재가 되어야 한다는 뜻이다. 한 번에 한 조각씩 점진적으로 도달하는 방법 외엔 이 목표를 달성할 방법은 없을 수도 있다. 그러나 이런 반복 과정을 통해 기존 파이썬 커뮤니티는 다른 곳에서 처음부터 새롭게 시작하지 않고 파이썬 언어와 함께 마이그레이션할 수 있다.

GIL 없는 파이썬과 같은 일부 조각은 타입 특수화와 같은 다른 조각보다 더 클 수 있다. 그러나 중요한 것은 혁신이 지속적으로 이뤄진다는 점이다. 이를 활용하여 모두에게 이익이 되도록 하는 것은 파이썬 커뮤니티의 몫이다.
dl-itworldkorea@foundryco.com

관련자료

댓글 0
등록된 댓글이 없습니다.
Member Rank