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

Python 개선 제안 한국어 번역

PEP 669 – CPython을 위한 저영향 모니터링

Author:
Mark Shannon <mark at hotpy.org>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Created:
18-Aug-2021
Python-Version:
3.12
Post-History:
07-Dec-2021, 10-Jan-2022
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

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

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at sys.monitoring.

×

See PEP 1 for how to propose changes.

초록

CPython에서 프로파일러나 디버거를 사용하면 성능에 심각한 영향을 미칠 수 있습니다. 한 자릿수 배율의 성능 저하는 흔히 발생합니다.

이 PEP는 CPython에서 실행되는 Python 프로그램을 낮은 비용으로 모니터링할 수 있도록 하는 API를 제안합니다.

이 PEP는 구현을 명시하지 않지만, PEP 659의 퀵닝 단계를 사용하여 구현될 것으로 예상합니다.

관련 함수와 상수를 포함하는 sys.monitoring 네임스페이스를 추가합니다.

동기

개발자는 디버거, 프로파일러 및 이와 유사한 기타 도구를 사용하기 위해 불합리한 비용을 부담할 필요가 없어야 합니다.

C++ 및 Java 개발자는 디버거에서 프로그램을 최고 속도(또는 그에 매우 가까운 속도)로 실행할 수 있기를 기대합니다. Python 개발자도 이를 기대해야 합니다.

근거

이 퀵닝 메커니즘은 PEP 659이 제공하며, 실행 중인 Python 바이트코드를 동적으로 수정하는 방법을 제공합니다. 이러한 수정은 수정되는 코드 부분을 제외하면 비용이 거의 없으며, 수정되는 부분에도 비교적 적은 비용만 발생합니다. 이를 활용하면 3.10 이하에서는 불가능했던 효율적인 모니터링 메커니즘을 제공할 수 있습니다.

퀵닝을 사용하면 3.12에서 디버거로 실행되는 코드가 3.11에서 디버거 없이 실행되는 코드보다 뛰어난 성능을 보일 것으로 예상합니다. 프로파일링을 수행하면 실행 속도가 여전히 느려지지만, 3.11에서보다 훨씬 적게 느려집니다.

사양

Python 프로그램 모니터링은 이벤트에 대한 콜백 함수를 등록하고 이벤트 집합을 활성화하여 수행합니다.

이벤트 활성화와 콜백 함수 등록은 서로 독립적입니다.

콜백 등록과 이벤트 활성화는 모두 도구별로 수행합니다. 서로 다른 이벤트 집합에 응답하는 여러 도구를 사용할 수 있습니다.

sys.settrace()와 달리 이벤트와 콜백은 스레드별이 아니라 인터프리터별이라는 점에 유의하십시오.

이벤트

코드 객체가 실행되면 도구에 관심 대상이 될 수 있는 다양한 이벤트가 발생합니다. 도구는 이벤트를 활성화하고 콜백 함수를 등록하여 자신에게 적합한 방식으로 이러한 이벤트에 응답할 수 있습니다. 이벤트는 전역으로 설정하거나 개별 코드 객체에 설정할 수 있습니다.

3.12에서 CPython은 다음 이벤트를 지원합니다.

  • PY_START: Python 함수의 시작(호출 직후에 발생하며, 호출된 함수의 프레임이 스택에 있습니다)
  • PY_RESUME: throw() 호출을 제외한 Python 함수의 재개(제너레이터 및 코루틴 함수의 경우)
  • PY_THROW: throw() 호출로 Python 함수가 재개됩니다.
  • PY_RETURN: Python 함수에서 반환(반환 직전에 발생하며, 호출된 함수의 프레임이 스택에 있습니다)
  • PY_YIELD: 파이썬 함수에서 yield합니다( yield 직전에 발생하며, 호출된 함수의 프레임이 스택에 놓입니다).
  • PY_UNWIND: 예외 언와인딩 중에 파이썬 함수에서 종료합니다.
  • CALL: 파이썬 코드에서 호출합니다(호출 전에 이벤트가 발생합니다).
  • C_RETURN: 파이썬 함수를 제외한 모든 호출 가능 객체에서 반환합니다(반환 후에 이벤트가 발생합니다).
  • C_RAISE: 파이썬 함수를 제외한 모든 호출 가능 객체에서 예외가 발생합니다(종료 후에 이벤트가 발생합니다).
  • RAISE: STOP_ITERATION이벤트를 발생시키는 예외를 제외하고, 예외가 발생합니다.
  • EXCEPTION_HANDLED: 예외가 처리됩니다.
  • LINE: 직전 명령어와 줄 번호가 다른 명령어를 실행하려고 합니다.
  • INSTRUCTION – VM 명령어를 실행하려고 합니다.
  • JUMP – 제어 흐름 그래프에서 무조건 점프가 수행됩니다.
  • BRANCH – 조건부 분기가 선택됩니다(또는 선택되지 않습니다).
  • STOP_ITERATION – 인공적인 StopIteration이 발생합니다. The STOP_ITERATION event를 참조하십시오.

앞으로 더 많은 이벤트가 추가될 수 있습니다.

모든 이벤트는 events 네임스페이스에서 sys.monitoring의 속성이 됩니다. 모든 이벤트는 2의 거듭제곱인 정수로 표현되므로, | 연산자를 사용하여 결합할 수 있습니다.

이벤트는 세 그룹으로 나뉩니다.

로컬 이벤트

로컬 이벤트는 프로그램의 정상 실행과 관련되며, 명확하게 정의된 위치에서 발생합니다. 모든 로컬 이벤트는 비활성화할 수 있습니다. 로컬 이벤트는 다음과 같습니다.

  • PY_START
  • PY_RESUME
  • PY_RETURN
  • PY_YIELD
  • CALL
  • LINE
  • INSTRUCTION
  • JUMP
  • BRANCH
  • STOP_ITERATION

보조 이벤트

보조 이벤트는 다른 이벤트처럼 모니터링할 수 있지만, 다른 이벤트에 의해 제어됩니다.

  • C_RAISE
  • C_RETURN

C_RETURNC_RAISE 이벤트는 CALL 이벤트에 의해 제어됩니다. 해당 CALL 이벤트가 모니터링되는 경우에만 C_RETURNC_RAISE 이벤트를 확인할 수 있습니다.

기타 이벤트

기타 이벤트는 프로그램의 특정 위치에 반드시 연결되는 것은 아니며 개별적으로 비활성화할 수 없습니다.

모니터링할 수 있는 기타 이벤트는 다음과 같습니다.

  • PY_THROW
  • PY_UNWIND
  • RAISE
  • EXCEPTION_HANDLED

STOP_ITERATION 이벤트

PEP 380에서는 제너레이터 또는 코루틴에서 값을 반환할 때 StopIteration 예외가 발생한다고 명시합니다. 그러나 이는 값을 반환하는 매우 비효율적인 방법이므로, 일부 Python 구현, 특히 CPython 3.12 이상에서는 다른 코드에 예외가 표시되는 경우가 아니면 예외를 발생시키지 않습니다.

제너레이터와 코루틴의 속도를 저하시키지 않으면서 도구가 실제 예외를 모니터링할 수 있도록 STOP_ITERATION 이벤트가 제공됩니다. STOP_ITERATIONRAISE와 달리 로컬에서 비활성화할 수 있습니다.

도구 식별자

VM은 한 번에 최대 6개의 도구를 지원할 수 있습니다. 이벤트를 등록하거나 활성화하기 전에 도구는 식별자를 선택해야 합니다. 식별자는 0부터 5까지의 범위에 있는 정수입니다.

sys.monitoring.use_tool_id(id, name:str) -> None
sys.monitoring.free_tool_id(id) -> None
sys.monitoring.get_tool(id) ->  str | None

sys.monitoring.use_tool_idid가 사용 중이면 ValueError를 발생시킵니다. sys.monitoring.get_toolid가 사용 중이면 해당 도구의 이름을 반환하고, 그렇지 않으면 None을 반환합니다.

VM은 이벤트와 관련하여 모든 ID를 동일하게 처리하지만, 도구 간 협력을 쉽게 하기 위해 다음 ID는 미리 정의되어 있습니다.:

sys.monitoring.DEBUGGER_ID = 0
sys.monitoring.COVERAGE_ID = 1
sys.monitoring.PROFILER_ID = 2
sys.monitoring.OPTIMIZER_ID = 5

ID를 설정해야 할 의무는 없으며, 이미 사용 중인 ID를 도구가 사용하는 것을 막는 요소도 없습니다. 그러나 도구는 고유한 ID를 사용하고 다른 도구를 존중하는 것이 권장됩니다.

예를 들어 디버거가 연결되어 있고 DEBUGGER_ID가 사용 중이라면, 아무 일도 없던 것처럼 계속 진행하기보다 오류를 보고해야 합니다.

OPTIMIZER_ID는 Cinder 또는 PyTorch와 같이 Python 코드를 최적화하려는 도구를 위해 제공되지만, 더 넓은 컨텍스트에 따라 최적화할 대상을 결정해야 합니다.

전역으로 이벤트 설정하기

모니터링되는 이벤트 집합을 수정하여 이벤트를 전역적으로 제어할 수 있습니다.

  • sys.monitoring.get_events(tool_id:int)->int는 모든 활성 이벤트를 나타내는 int를 반환합니다.
  • sys.monitoring.set_events(tool_id:int, event_set: int)event_set에 설정된 모든 이벤트를 활성화합니다. tool_id가 사용 중이 아니면 ValueError를 발생시킵니다.

기본적으로 활성화된 이벤트는 없습니다.

코드 객체별 이벤트

이벤트는 코드 객체별로도 제어할 수 있습니다.

  • sys.monitoring.get_local_events(tool_id:int, code: CodeType)->intcode에 대한 모든 로컬 이벤트를 반환합니다.
  • sys.monitoring.set_local_events(tool_id:int, code: CodeType, event_set: int)code에 대해 event_set에 설정된 모든 로컬 이벤트를 활성화합니다. tool_id가 사용 중이 아니면 ValueError를 발생시킵니다.

로컬 이벤트는 전역 이벤트에 추가되지만, 전역 이벤트를 가리지는 않습니다. 즉, 로컬 이벤트와 관계없이 모든 전역 이벤트가 코드 객체에 대해 트리거됩니다.

콜백 함수 등록

이벤트에 호출 가능 객체를 등록하려면 다음을 호출하십시오.:

sys.monitoring.register_callback(tool_id:int, event: int, func: Callable | None) -> Callable | None

지정된 tool_idevent에 대해 다른 콜백이 등록되어 있으면 해당 콜백의 등록이 해제되고 반환됩니다. 그렇지 않으면 register_callbackNone을 반환합니다.

sys.monitoring.register_callback(tool_id, event, None)을 호출하여 함수를 등록 해제할 수 있습니다.

콜백 함수는 언제든지 등록하고 등록 해제할 수 있습니다.

콜백 함수를 등록하거나 등록 해제하면 sys.audit 이벤트가 생성됩니다.

콜백 함수 인수

활성 이벤트가 발생하면 등록된 콜백 함수가 호출됩니다. 이벤트마다 다음과 같이 콜백 함수에 서로 다른 인수가 제공됩니다.

  • PY_STARTPY_RESUME:
    func(code: CodeType, instruction_offset: int) -> DISABLE | Any
    
  • PY_RETURNPY_YIELD:
    func(code: CodeType, instruction_offset: int, retval: object) -> DISABLE | Any
  • CALL, C_RAISEC_RETURN:
    func(code: CodeType, instruction_offset: int, callable: object, arg0: object | MISSING) -> DISABLE | Any

    인수가 없으면 arg0MISSING으로 설정됩니다.

  • RAISEEXCEPTION_HANDLED:
    func(code: CodeType, instruction_offset: int, exception: BaseException) -> DISABLE | Any
  • LINE:
    func(code: CodeType, line_number: int) -> DISABLE | Any
  • BRANCH:
    func(code: CodeType, instruction_offset: int, destination_offset: int) -> DISABLE | Any

    destination_offset은 코드가 다음에 실행할 위치라는 점에 유의하십시오. 실행되지 않은 분기의 경우 이는 분기 다음 명령의 오프셋입니다.

  • INSTRUCTION:
    func(code: CodeType, instruction_offset: int) -> DISABLE | Any

콜백 함수가 DISABLE을 반환하면 (code, instruction_offset)에 대해서는 sys.monitoring.restart_events()가 호출될 때까지 해당 함수가 더 이상 호출되지 않습니다. 이 기능은 이벤트를 한 번만 확인하는 데 관심이 있는 커버리지 도구 및 기타 도구를 위해 제공됩니다.

sys.monitoring.restart_events()는 특정 도구에 한정되지 않으므로, 도구는 자신이 DISABLE하도록 선택한 이벤트를 수신할 수 있도록 준비해야 합니다.

콜백 함수의 이벤트

해당 콜백을 등록한 도구에 대해서는 콜백 함수와 그 피호출 함수에서 이벤트가 일시 중지됩니다.

즉, 다른 도구는 다른 도구의 콜백 함수에서 발생하는 이벤트를 확인하게 됩니다. 이는 프로파일링 도구를 디버깅할 때 유용할 수 있지만, 디버거 도구가 프로파일에 나타나므로 프로파일을 오해하게 만들 수 있습니다.

이벤트 순서

하나의 명령어가 여러 이벤트를 트리거하면 다음 순서로 발생합니다.

  • LINE
  • INSTRUCTION
  • 그 밖의 모든 이벤트(명령어당 이러한 이벤트 중 하나만 발생할 수 있음)

각 이벤트는 ID 오름차순으로 도구에 전달됩니다.

“call” 이벤트 그룹

대부분의 이벤트는 독립적이므로, 한 이벤트를 설정하거나 비활성화해도 다른 이벤트에는 영향을 주지 않습니다. 그러나 CALL, C_RAISEC_RETURN 이벤트는 하나의 그룹을 구성합니다. 이러한 이벤트 중 하나라도 설정되거나 비활성화되면 그룹의 모든 이벤트도 설정되거나 비활성화됩니다. CALL 이벤트를 비활성화해도 이에 대응하는 C_RAISE 또는 C_RETURN은 비활성화되지 않지만, 이후의 모든 이벤트는 비활성화됩니다.

sys.monitoring 네임스페이스의 속성

  • def use_tool_id(id)->None
  • def free_tool_id(id)->None
  • def get_events(tool_id: int)->int
  • def set_events(tool_id: int, event_set: int)->None
  • def get_local_events(tool_id: int, code: CodeType)->int
  • def set_local_events(tool_id: int, code: CodeType, event_set: int)->None
  • def register_callback(tool_id: int, event: int, func: Callable)->Optional[Callable]
  • def restart_events()->None
  • DISABLE: object
  • MISSING: object

“디버그 전용” 기능에 대한 접근

표준 라이브러리의 일부 기능은 일반 코드에서 접근할 수 없지만 디버거에서는 접근할 수 있습니다. 예를 들어, 지역 변수나 줄 번호를 설정하는 기능이 그러합니다.

이러한 기능은 콜백 함수에서 사용할 수 있습니다.

하위 호환성

이 PEP는 대부분 하위 호환성을 유지합니다.

일부 호환성 문제는 PEP 523과 관련되어 있으며, PEP 523 플러그인의 동작은 VM의 제어 밖에 있기 때문입니다. 이 PEP의 의미 체계를 준수하도록 보장하는 것은 PEP 523 플러그인의 책임입니다. VM의 상태를 변경하지 않고 실행을 _PyEval_EvalFrameDefault()에 위임하는 단순한 플러그인은 계속 작동해야 합니다.

sys.settrace()sys.setprofile()은 각각 도구 6과 7인 것처럼 동작하므로 이 PEP와 함께 사용할 수 있습니다.

이는 sys.settrace()sys.setprofile()이 모든 PEP 523 플러그인에서 올바르게 작동하지 않을 수 있음을 의미합니다. 다만 위에서 설명한 단순한 PEP 523 플러그인은 문제가 없을 것입니다.

성능

활성화된 이벤트가 없으면 이 PEP는 성능에 작지만 긍정적인 영향을 주어야 합니다. 실험 결과 sys.settrace()를 직접 지원하지 않음으로써 1~2%의 속도 향상이 나타났습니다.

성능은 sys.settrace()의 경우 거의 동일할 것입니다. 성능은 sys.setprofile()의 경우 더 좋아질 것입니다. 그러나 sys.settrace()sys.setprofile()에 의존하는 도구는 이 PEP에서 제공하는 API를 사용하면 훨씬 더 빠르게 만들 수 있습니다.

예를 들어 디버거처럼 적은 수의 이벤트가 활성화된 경우 콜백의 오버헤드는 sys.settrace()에 비해 몇 자릿수나 작으며 PEP 523를 사용하는 것보다 훨씬 저렴합니다.

모든 콜백에서 DISABLE를 반환하면 매우 낮은 비용으로 커버리지 도구를 구현할 수 있습니다.

예를 들어 LINE을 사용하는 것처럼 계측이 많이 적용된 코드에서는 성능이 sys.settrace()보다 좋아야 하지만, 콜백에 소요되는 시간이 성능을 지배하므로 그 차이가 크지는 않을 것입니다.

향후 CPython 버전과 같은 최적화 가상 머신(그리고 PyPy가 이 API를 지원하기로 한다면)에서는 장시간 실행되는 프로그램 도중 활성 이벤트 집합을 변경하는 작업에 매우 큰 비용이 들 수 있으며, 비최적화를 트리거하므로 수백 밀리초가 소요될 수도 있습니다. 이러한 비최적화가 발생하면 VM이 계측된 코드를 다시 최적화할 수 있으므로 성능은 회복될 것입니다.

일반적으로 다음 작업은 빠르다고 간주할 수 있습니다.

  • def get_events(tool_id: int)->int
  • def get_local_events(tool_id: int, code: CodeType)->int
  • def register_callback(tool_id: int, event: int, func: Callable)->Optional[Callable]
  • def get_tool(tool_id) -> str | None

다음 작업은 더 느리지만 특별히 느리지는 않습니다.

  • def set_local_events(tool_id: int, code: CodeType, event_set: int)->None

그리고 다음 작업은 느리다고 간주해야 합니다.

  • def use_tool_id(id, name:str)->None
  • def free_tool_id(id)->None
  • def set_events(tool_id: int, event_set: int)->None
  • def restart_events()->None

느린 연산이 얼마나 느린지는 언제 수행되는지에 따라 달라집니다. 프로그램 초기에, 모듈이 로드되기 전에 수행하면 상당히 적은 비용으로 처리할 수 있습니다.

메모리 소비

사용되지 않을 때 이 PEP는 메모리 사용량에 무시할 수 있을 정도의 변화만 일으킵니다.

메모리가 사용되는 방식은 매우 구현 세부 사항에 해당합니다. 그러나 3.12에서는 코드 객체당 추가 메모리 사용량이 대략 다음과 같을 것으로 예상합니다.

Events
Tools Others LINE INSTRUCTION
One None ≈40% ≈80%
Two or more ≈40% ≈120% ≈200%

보안 관련 사항

실행 중인 코드를 수정하도록 허용하면 일부 보안 관련 문제가 발생하지만, 새로운 코드를 생성하고 호출할 수 있는 것보다 더 심각하지는 않습니다.

위에 나열된 모든 새 함수는 감사 후크를 트리거합니다.

구현

여기에서는 CPython 3.12를 위한 제안된 구현을 설명합니다. 이후 버전의 CPython 및 다른 Python 구현의 실제 구현은 상당히 달라질 수 있습니다.

이 PEP의 제안된 구현은 PEP 659에 설명된 CPython 3.11의 퀵닝 단계를 기반으로 구축됩니다. 계측은 퀵닝과 거의 같은 방식으로 작동하며, 필요에 따라 바이트코드가 계측된 바이트코드로 대체됩니다.

예를 들어 CALL 이벤트가 켜져 있으면 모든 호출 명령어가 INSTRUMENTED_CALL 명령어로 대체됩니다.

이는 특수화와 충돌하며, 등록된 호출 가능 객체를 호출하는 오버헤드에 더해 성능 저하를 일으킵니다.

활성 이벤트 집합이 변경되면 VM은 모든 스레드의 호출 스택에 있는 모든 코드 객체를 즉시 업데이트합니다. 또한 호출될 때 모든 코드 객체가 올바르게 계측되도록 인플레이스 트랩을 설정합니다. 따라서 활성 이벤트 집합 변경은 가능한 한 드물게 수행해야 합니다. 상당히 비용이 많이 드는 작업일 수 있기 때문입니다.

RAISE와 같은 다른 이벤트는 코드 계측에 의존하지 않고, 해당 이벤트가 발생할 때 런타임 검사를 수행하므로 저렴하게 켜거나 끌 수 있습니다.

계측이 필요한 이벤트의 정확한 집합은 구현 세부 사항이지만, 현재 설계에서는 다음 이벤트에 계측이 필요합니다.

  • PY_START
  • PY_RESUME
  • PY_RETURN
  • PY_YIELD
  • CALL
  • LINE
  • INSTRUCTION
  • JUMP
  • BRANCH

계측된 각 바이트코드에는 계측이 어느 도구에 적용되는지 기록하기 위한 8비트의 추가 정보가 필요합니다. LINEINSTRUCTION 이벤트에는 원래 명령어 또는 다른 계측과 겹치는 경우 계측된 명령어까지 저장해야 하므로 추가 정보가 필요합니다.

도구 구현

이 PEP의 철학은 서드파티 모니터링 도구가 고성능을 달성할 수 있어야 한다는 것이지, 그렇게 하는 일이 쉬워야 한다는 것이 아닙니다.

이벤트를 사용자에게 의미 있는 데이터로 변환하는 것은 도구의 책임입니다.

모든 이벤트에는 비용이 따르므로, 도구는 필요한 정보를 계속 제공하면서도 가장 적은 빈도로 발생하는 이벤트 집합을 사용하도록 해야 합니다.

디버거

중단점 삽입

코드 객체별 이벤트를 LINE 또는 INSTRUCTION으로 설정하고, 중단점과 일치하지 않는 모든 이벤트에 대해 DISABLE를 반환하여 중단점을 삽입할 수 있습니다.

단일 단계 실행

디버거는 일반적으로 단일 명령어 또는 한 줄씩 실행을 진행하는 기능을 제공합니다.

중단점과 마찬가지로, 코드 객체별 이벤트를 설정하여 단일 단계 실행을 구현할 수 있습니다. 정상 실행을 재개하는 즉시 로컬 이벤트 설정을 해제할 수 있습니다.

연결

디버거는 코드 객체를 처음 만났을 때 이를 알리도록 PY_STARTPY_RESUME 이벤트를 사용하여 필요한 중단점을 삽입할 수 있습니다.

커버리지 도구

커버리지 도구는 제어 그래프의 어느 부분이 실행되었는지 추적해야 합니다. 이를 위해 PY_ 이벤트와 JUMPBRANCH에 등록해야 합니다.

실행이 완료된 후 이 정보를 다시 라인 기반 보고서로 변환할 수 있습니다.

프로파일러

단순한 프로파일러는 호출에 관한 정보를 수집해야 합니다. 이를 위해 프로파일러는 다음 이벤트에 등록해야 합니다.

  • PY_START
  • PY_RESUME
  • PY_THROW
  • PY_RETURN
  • PY_YIELD
  • PY_UNWIND
  • CALL
  • C_RAISE
  • C_RETURN

라인 기반 프로파일러

라인 기반 프로파일러는 LINEJUMP 이벤트를 사용할 수 있습니다. 프로파일러 구현자는 LINE 이벤트를 계측하면 성능에 큰 영향을 미친다는 점을 인지해야 합니다.

Note

계측 프로파일러에는 상당한 오버헤드가 발생하며 프로파일링 결과가 왜곡됩니다. 정확한 호출 횟수가 필요하지 않다면 통계 프로파일러를 사용해 보십시오.

거부된 아이디어

이 PEP의 초안에서는 VM이 모니터링 명령을 삽입하도록 하는 대신 사용자가 이를 삽입할 책임을 지도록 제안했습니다. 그러나 이는 도구에 지나치게 큰 부담을 주며 디버거를 연결하는 일을 거의 불가능하게 만들 것입니다.

이 PEP의 이전 버전에서는 이벤트를 enums로 저장하도록 제안했습니다.:

class Event(enum.IntFlag):
    PY_START = ...

그러나 그렇게 하면 enum 모듈이 로드되기 전에 코드를 모니터링할 수 없고 불필요한 오버헤드가 발생할 수 있습니다.