Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 611 – 100만 제한

Author:
Mark Shannon <mark at hotpy.org>
Status:
Withdrawn
Type:
Standards Track
Created:
05-Dec-2019
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PR은 Python 코드와 그 구현의 다양한 측면에 대해 100만(1 000 000)의 소프트 제한과 그보다 큰 하드 제한을 제안합니다.

Python 언어는 많은 기능에 대해 제한을 명시하지 않습니다. 이러한 값에 아무런 제한을 두지 않으면 적어도 겉보기에는 프로그래머의 자유가 늘어나는 것처럼 보이지만, 실제로 CPython VM과 다른 Python 가상 머신은 암묵적인 제한을 두거나 제한이 천문학적으로 크다고 가정할 수밖에 없으며, 이는 비용이 큽니다.

이 PR은 100만의 제한을 두어야 할 여러 기능을 나열합니다.

CPython의 하드 제한은 800만(8 000 000)이 될 것입니다.

동기

가상 머신에서 표현해야 하는 값은 많습니다. 이러한 값에 대해 제한이 명시되지 않으면, 그 표현은 비효율적이거나 오버플로에 취약할 수밖에 없습니다. CPython 가상 머신은 줄 번호, 스택 오프셋, 명령어 오프셋과 같은 값을 32비트 값으로 표현합니다. 이는 비효율적이며 잠재적으로 안전하지 않습니다.

실제 값을 표현하는 데 십여 비트 이상이 필요한 경우는 드물기 때문에 비효율적입니다.

악의적이거나 잘못 생성된 코드로 인해 값이 232을 초과할 수 있기 때문에 안전하지 않습니다.

예를 들어, 줄 번호는 내부적으로 32비트 값으로 표현됩니다. 모듈이 수천 줄을 넘는 경우가 거의 없다는 점을 고려하면 이는 비효율적입니다. 비효율적임에도 불구하고 오버플로에는 여전히 취약합니다. 공격자가 수십억 개의 줄바꿈 문자가 포함된 모듈을 쉽게 만들 수 있기 때문입니다.

메모리 접근은 대개 현대 CPU 성능의 제한 요인입니다. 자료 구조를 더 촘촘하게 패킹하면 지역성이 향상되고 메모리 대역폭이 줄어듭니다. 대신 (시프트와 마스킹 때문에) ALU 사용량이 약간 늘어납니다. 중요한 값을 20비트에 안전하게 저장할 수 있으면, 다음을 포함하되 이에 국한되지 않는 여러 자료 구조에서 메모리를 절약할 수 있습니다:

  • 프레임 객체
  • 객체 헤더
  • 코드 객체

더 효율적인 명령어 형식을 사용하여 인터프리터 디스패치 속도를 높일 수 있는 가능성도 있습니다.

이것은 감수할 만한 트레이드오프입니까?

어떤 형태로든 제한을 두면 누군가의 작업이 더 어려워질 수 있다는 단점이 있습니다. 예를 들어, 모듈 크기를 100만 줄 이내로 유지하는 코드 제너레이터를 작성하기가 더 어려워질 수 있습니다. 그러나 많은 코드 생성기를 작성해 본 저자의 의견으로는, 이러한 제한이 실제로 문제가 될 가능성은 극히 낮습니다.

이러한 제한의 장점은 CPython이든 PyPy든 다른 어떤 구현이든 런타임 구현자에게 성능을 개선할 수 있는 자유를 준다는 것입니다. 저자는 전 세계적으로 Python 프로그램을 실행하는 비용을 0.1%만 줄이더라도 그 잠재적 가치가 소수의 코드 생성기를 수정하는 비용을 크게 넘어설 것이라고 믿습니다.

근거

모듈의 코드 줄 수나 지역 변수의 개수와 같은 값에 제한을 두면 구현의 용이성과 가상 머신의 효율성 측면에서 상당한 이점이 있습니다. 제한이 충분히 크다면 언어 사용자에게는 아무런 악영향이 없습니다.

이 값들에 고정되어 있지만 충분히 큰 한도를 정하면, 사람 프로그래머에게는 아무런 불편을 주지 않고 코드 생성기에만 극히 드물게 문제를 일으키면서 안전성과 효율성을 모두 확보할 수 있습니다.

백만

“백만”이라는 값은 기억하기가 매우 쉽습니다.

백만이라는 한도는 대체로 런타임 크기가 아니라 사람이 작성한 코드에 대한 한도입니다.

한 모듈에 백만 줄이 있다는 것은 터무니없을 정도로 코드가 집중된 것입니다. Python 표준 라이브러리 전체도 약 66만 줄 정도이며, 1600개의 파일에 나뉘어 있습니다.

Java 가상 머신(JVM) [1] 은 여기서 다루는 것과 비슷한 많은 프로그램 요소에 대해 216-1 (65535)이라는 제한을 명시합니다. 이 한도 덕분에 제한된 값들을 16비트에 담을 수 있으며, 이는 매우 효율적인 기계 표현입니다. 그러나 실제로는 코드 생성기가 이 제한을 꽤 쉽게 넘기며, 저자는 이미 216 줄을 넘는 기존 Python 코드가 있다는 것을 알고 있습니다.

팔백만이라는 하드 한도는 23비트에 들어가며, 기계 표현에 그만큼 편리하지는 않지만 여전히 상당히 간결합니다. 팔백만이라는 한도는 효율성 이점을 얻을 만큼 충분히 작으면서도(23비트에 불과함), 사용자에게 영향을 주지 않을 만큼 충분히 큽니다(그렇게 큰 모듈을 작성한 사람은 지금까지 아무도 없었습니다).

생성된 코드가 이 제한을 초과할 수도 있지만, 코드 제너레이터가 제한에 맞도록 출력을 수정하기는 쉽습니다. 저자는 Java 코드를 생성하다가 JVM의 64K 제한에 적어도 두 번 부딪힌 적이 있습니다. 우회 방법은 비교적 간단했으며, 바이트코드나 코드 줄 수의 한도가 백만이었다면 필요하지 않았을 것입니다.

필요한 경우, 백만 한도를 넘는 프로그램에 대해서는 소프트 한도를 늘릴 수 있습니다.

소프트 한도를 백만으로 두면 오류를 일으켜 즉각적인 수정을 강요하지 않으면서도 문제가 있는 코드에 대해 경고할 수 있습니다. 또한 동적 최적화기가 인라인 검사 없이 더 간결한 형식을 사용할 수 있게 해 줍니다.

명세

이 PR은 다음 언어 기능과 런타임 값들에 백만의 소프트 한도를 둘 것을 제안합니다.

  • 모듈의 소스 코드 줄 수
  • 코드 객체의 바이트코드 명령어 수.
  • 코드 객체의 지역 변수 수와 스택 사용량의 합.
  • 실행 중인 인터프리터의 클래스 수.
  • Python 코드의 재귀 깊이.

클래스 수가 백만에 도달하기 전에 메모리 제약이 제한 요인이 될 가능성이 높습니다.

재귀 깊이

재귀 깊이 제한은 순수 Python 코드에만 적용됩니다. C와 같은 외부 언어로 작성된 코드는 하드웨어 스택을 소비할 수 있으므로 재귀 깊이가 수천 정도로 제한될 수 있습니다. 하드웨어 스택이 한계에 가까워지면 구현이 예외를 발생시킬 것으로 예상됩니다. Python 호출과 C 호출이 섞인 코드에서는 하드웨어 제한이 먼저 적용될 가능성이 가장 높습니다. 하드웨어 재귀의 크기는 런타임에 달라질 수 있으며 겉으로 드러나지 않습니다.

소프트 한도와 하드 한도

하드 제한이 소프트 제한과 같은 값인 경우를 제외하고, 구현은 소프트 제한을 초과할 때마다 경고를 발생시켜야 합니다. 하드 제한을 초과하면 예외를 발생시켜야 합니다.

구현에 따라 서로 다른 하드 제한이 적용될 수 있습니다. 경우에 따라서는 하드 제한이 소프트 제한보다 낮을 수도 있습니다. 예를 들어, 많은 micropython 포트는 이처럼 큰 제한을 지원하기 어려울 것입니다.

제한 조회 및 수정

런타임에 소프트 제한을 조회하거나 수정할 수 있도록 sys 모듈에 하나 이상의 함수가 제공될 것이지만, 제한을 하드 제한보다 높게 올릴 수는 없습니다.

추론되는 제한

이러한 제한은 명세의 일부가 아니지만, 코드 객체의 바이트코드 명령어 수에 대한 제한으로부터 백만 미만의 제한을 추론할 수 있습니다. 백만 개가 넘는 상수를 적재하거나 백만 개가 넘는 이름을 사용하기에는 명령어가 부족하기 때문입니다.

  • 코드 객체 내의 서로 다른 이름의 수.
  • 코드 객체 내의 상수의 수.

이러한 제한을 부과함으로써 CPython이 얻는 이점은 다음과 같습니다:

모듈의 코드 줄 수 및 코드 객체 제한.

소스 코드를 바이트코드로 컴파일하거나 프로파일링 또는 디버깅을 위해 바이트코드를 수정할 때는 중간 형식이 필요합니다. 피연산자를 23비트로 제한하면 명령어를 간결한 64비트 형식으로 표현할 수 있어 명령어 시퀀스를 매우 빠르게 순회할 수 있습니다.

피연산자를 23비트(상대 브랜치는 24비트)로 두면 추가 EXTENDED_ARG 명령어 없이도 명령어를 32비트에 맞출 수 있습니다. 피연산자가 엄격하게 해당 명령어 안에만 국한되므로 디스패치가 개선됩니다. 이것이 성능 향상에 도움이 될지는 분명하지 않으며, 단지 가능한 것의 한 예일 뿐입니다.

모듈의 줄 수를 제한하는 것의 이점은 주로 그로부터 암묵적으로 따라오는 바이트코드에 대한 제한에 있습니다. 구현 입장에서는 모듈당 줄 수가 아니라 코드 객체당 명령어 수가 백만으로 제한되는 것이 더 중요하지만, 백만 줄 제한이 설명하기 훨씬 쉽습니다. 백만이라는 일관된 제한이 그저 기억하기가 더 쉽습니다. 보장되지는 않지만 줄 수 제한에 먼저 도달할 가능성이 가장 높으며, 따라서 개발자에게 더 이해하기 쉬운 오류 메시지를 제공하게 될 것입니다.

실행 중인 인터프리터의 전체 클래스 수

이 제한은 객체 헤더의 크기를 상당히 줄일 수 있는 잠재력을 가지고 있습니다.

현재 객체는 참조가 없는 객체(int, float, str 등)의 경우 2워드 헤더를, 참조가 있는 객체의 경우 4워드 헤더를 가집니다. 최대 클래스 수를 줄이면 클래스 참조를 위한 공간을 64비트에서 32비트 미만으로 줄일 수 있어 훨씬 더 간결한 헤더가 가능해집니다.

예를 들어, 초간결 헤더 형식은 다음과 같을 수 있습니다:

struct header {
    uint32_t gc_flags:6; /* Needs finalisation, might be part of a cycle, etc. */
    uint32_t class_id:26; /* Can be efficiently mapped to address by ensuring suitable alignment of classes */
    uint32_t refcount; /* Limited memory or saturating */
}

이 형식을 사용하면 64비트 머신에서 슬롯이 없는 Python 객체의 크기가 40바이트에서 16바이트로 줄어들 것입니다.

64비트 머신에서 32비트 참조 횟수를 사용하는 방법은 두 가지가 있다는 점에 유의하십시오. 하나는 각 서브 인터프리터의 메모리를 32Gb로 제한하는 것입니다. 다른 하나는 포화 참조 횟수를 사용하는 것으로, 약간 더 느리지만 메모리를 무제한으로 할당할 수 있습니다.

강제 적용

Python 구현이 이 제한을 반드시 강제해야 하는 것은 아닙니다. 그러나 성능을 해치지 않고 제한을 강제할 수 있다면 강제해야 합니다.

CPython은 다음과 같이 제한을 강제할 것으로 예상됩니다:

  • 모듈의 소스 코드 줄 수: 버전 3.9부터.
  • 코드 객체의 바이트코드 명령어 수: 3.9부터.
  • 코드 객체의 지역 변수 수와 스택 사용량의 합: 3.9부터.
  • 실행 중인 인터프리터의 클래스 수: 아마도 3.10부터이며, 3.9에서는 경고를 낼 수도 있습니다.

CPython의 하드 제한

CPython은 위의 모든 값에 하드 제한을 강제할 것입니다. 하드 제한 값은 800만이 될 것입니다.

이론적으로는 기계가 생성한 일부 코드가 위 제한 중 하나 이상을 초과할 가능성이 있습니다. 저자는 그러한 일이 일어날 가능성이 극히 낮으며, 일어나더라도 코드 제너레이터의 출력 단계를 수정하면 쉽게 해결할 수 있다고 생각합니다.

우리는 위 제한이 주는 성능상의 이점을 가능한 한 빨리 얻고자 합니다. 이를 위해 CPython은 버전 3.9부터 제한을 적용하기 시작할 것입니다. 전환을 쉽게 하고 호환성 문제를 최소화하기 위해 초기 제한은 1,600만으로 하고, 이후 버전에서 800만으로 줄일 것입니다.

하위 호환성

CPython이 실제로 강제할 하드 제한은 다음과 같습니다:

버전 하드 제한
3.9 1,600만
3.10부터 800만

100만 제한을 초과하는 코드 생성기가 드물다는 점과 그러한 코드 생성기가 일반적으로 사용되는 환경을 고려하면, 제한 대상인 수량 중 하나라도 100만을 초과할 경우 3.9부터 경고를 내기 시작하는 것이 합리적이라고 판단됩니다.

역사적으로 재귀 제한은 1000으로 설정되어 왔습니다. 이 값이 작다는 점에 암묵적으로 의존하는 코드가 깨지지 않도록, 소프트 재귀 제한은 다음과 같이 점진적으로 늘어날 것입니다:

버전 소프트 제한
3.9 4 000
3.10 16 000
3.11 64 000
3.12 125 000
3.13 100만

하드 제한은 즉시 800만으로 설정됩니다.

기타 구현

CPython 이외의 Python 구현은 목적이 서로 다르므로 다른 제한이 적절할 수 있습니다. 제한을 명확하게 문서화한다면 이는 허용됩니다.

범용 구현

PyPy와 같은 범용 구현은 100만 제한을 사용해야 합니다. 최대한의 호환성이 목표라면 3.9부터 3.11까지의 CPython 동작도 따라야 합니다.

특수 목적 구현

특수 목적 구현은 명확하게 문서화하기만 한다면 더 낮은 제한을 사용할 수 있습니다. 예를 들어 MicroPython처럼 임베디드 시스템용으로 설계된 구현은 수천 정도로 낮은 제한을 둘 수도 있습니다.

보안 영향

미미합니다. 이는 모든 Python 가상 머신의 공격 표면을 조금 줄여 줍니다.

참조 구현

아직 없습니다. PEP가 승인되면 CPython에 구현될 예정입니다.

기각된 아이디어

Tal Einat은 컴파일 시점에 하드 제한을 더 높게 수정할 수 있게 하자고 제안했습니다. 현재의 232 제한이 문제가 된 적이 없고, 220과 232 사이의 제한을 허용하는 데 따르는 실질적인 이점이 그러한 기능을 지원하는 데 드는 추가적인 코드 복잡성에 비해 작아 보이므로 이 제안은 거부되었습니다.

미해결 문제

아직 없습니다.

참고 문헌

https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf