Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

디버거 시각화 도구

이들은 일반적으로 디버거가 정보를 표시하기 전의 마지막 단계이지만, 그 결과가 IDE의 디버거 API와 같은 디버그 어댑터를 거쳐 전달될 수도 있습니다.

“시각화 도구(Visualizer)“라는 용어는 다소 부적절한 명칭입니다. 진정한 목표는 단순히 출력을 보기 좋게 만드는 것이 아니라, 사용자가 상호작용할 수 있는 최대한 유용한 인터페이스를 제공하는 것입니다. 많은 경우 이는 원래 타입을 Rust 표현에 최대한 가깝게 재구성하는 것을 의미하지만, 항상 그런 것은 아닙니다.

시각화 도구 인터페이스를 사용하면 “합성 자식(synthetic children)“을 생성할 수 있습니다. 합성 자식이란 디버그 정보에는 존재하지 않지만 언어와 타입 자체의 불변 조건으로부터 도출할 수 있는 필드를 말합니다. 간단한 예로, Vec<T>의 *mut u8 힙 포인터, 길이, 용량만 보는 대신 그 요소들과 상호작용할 수 있도록 하는 것을 들 수 있습니다.

rust-lldb, rust-gdb, rust-windbg.cmd

이 지원 스크립트들은 Rust 툴체인과 함께 배포됩니다. 이 스크립트들은 적절한 디버거와 툴체인의 시각화 스크립트를 찾은 다음, 디버기를 실행하거나 디버기에 연결하기 전에 시각화 스크립트를 로드하도록 적절한 인자로 디버거를 실행합니다.

#![debugger_visualizer]

이 속성을 사용하면 Rust 라이브러리 작성자가 자신의 타입에 대한 프리티 프린터(pretty printer)를 라이브러리 자체에 포함할 수 있습니다. 이러한 프리티 프린터는 일반적인 시각화 도구와 같은 형식이지만, 컴파일된 바이너리에 직접 내장됩니다. 이 스크립트들은 디버거가 자동으로 불러오므로 사용자에게 매끄러운 경험을 제공합니다. 이 속성은 현재 GDB 및 natvis 스크립트에 대해 동작합니다.

GDB Python 스크립트는 바이너리의 .debug_gdb_scripts 섹션에 포함됩니다(자세한 정보). Rustc는 이를 rustc_codegen_llvm/src/debuginfo/gdb.rs에서 수행합니다.

Natvis 파일은 /NATVIS 링커 옵션을 사용하여 PDB 디버그 정보에 내장할 수 있으며, 타입에 사용할 시각화 도구를 결정할 때 가장 높은 우선순위를 가집니다. 속성으로 지정된 파일은 CrateInfo::natvis_debugger_visualizers에 수집되며, 이후 rustc_codegen_ssa/src/back/linker.rs에서 링커 인자로 추가됩니다.

LLDB는 현재 지원되지 않지만, 향후 지원을 가능하게 할 수 있는 몇 가지 방법이 있습니다. 공식적으로 의도된 방법은 포매터 바이트코드를 이용하는 것입니다. 이는 GDB와 비슷한 경험을 제공하되, Python 스크립트 전체를 포함할 때 따르는 안전 문제는 없도록 하기 위해 만들어졌습니다. 연산 코드(opcode)는 제한적이지만, Python 시각화 스크립트와 거의 같은 방식으로 SBValue 및 SBType과 함께 동작합니다. 이를 구현하려면 일종의 DSL/미니 컴파일러를 작성해야 합니다.

또는 GDB의 전략을 그대로 따라 할 수도 있습니다. 바이너리에 전용 섹션을 만들고 그 안에 Python 스크립트를 포함하는 것입니다. LLDB가 이를 자동으로 로드하지는 않지만, Python API를 사용하면 디버그 정보의 원시 섹션에 접근할 수 있습니다. 이를 이용하면 전용 섹션에서 Python 스크립트를 추출한 다음, Rust 시각화 스크립트가 시작될 때 이를 로드할 수 있을 것입니다.

성능

시각화 도구 자체를 다루기 전에, 이들이 성능에 민감한 시스템의 일부라는 점에 유의해야 합니다. 잠시 격식을 벗어나는 점을 양해해 주십시오. 디버깅에 상당한 시간을 써야 한다면 저는 짜증이 납니다. 디버거를 기다려야 한다면 저는 화가 치밉니다.

이 시각화 도구에서 소비되는 1밀리초는 곧 사용자가 출력을 보기까지 1밀리초를 더 기다려야 한다는 뜻입니다. 이는 많은 컨테이너 타입이나 큰 컨테이너 타입을 포함하는 큰 스택 프레임에서 특히 고통스러울 수 있습니다. VSCode 같은 디버거 GUI는 스택 프레임 전체를 한 번에 요청하며, 이로 인해 프레임 안의 어떤 변수와도 상호작용할 수 있기까지 수십 초(심지어 수 분)의 지연이 발생할 수 있습니다.

Python 코드를 최적화한다는 생각을 꺼리는 경향이 있지만, 실제로 상당한 영향을 줄 수 있습니다. 코드를 빠르게 유지하도록 도와줄 컴파일러가 없다는 점을 기억하십시오. 간단한 변환조차 대신 수행해 주지 않습니다. Python은 굳이 최적화할 필요가 없다는 사람들의 온갖 잡음 속에서 Python 성능 팁을 찾기는 어려울 수 있으므로, 이 스크립트와 관련하여 염두에 둘 만한 몇 가지 사항을 소개합니다.

  • 모든 것이 할당을 수행하며, int조차 예외가 아닙니다
  • 가능하면 튜플을 사용하십시오. list는 사실상 Vec<Box<[Any]>>인 반면, 튜플은 Box<[Any]>에 해당합니다. 튜플은 간접 참조 계층이 하나 적고, 여분의 용량을 갖지 않으며, 늘어나거나 줄어들 수 없으므로 많은 경우에 유리할 수 있습니다. 추가적인 이점으로, Python은 크기가 20 이하인 모든 튜플의 기반 할당을 캐시하고 재사용합니다.
  • 정규식은 느리므로 간단한 문자열 조작으로 충분할 때는 피해야 합니다
  • 문자열은 불변이므로 많은 문자열 연산이 암묵적으로 내용을 복사합니다.
  • 큰 문자열 리스트를 연결할 때는 일반적으로 "".join(iterable_of_strings)가 가장 빠른 방법입니다.
  • 문자열을 괄호로 감싸는 것처럼 작고 간단한 문자열 변환에는 일반적으로 f-string이 가장 빠른 방법입니다.
  • 함수를 호출하는 행위 자체가 다소 느립니다(함수가 완전히 비어 있더라도 마찬가지입니다). 해당 코드 구간이 매우 자주 실행된다면 함수를 수동으로 인라이닝하는 것을 고려하십시오.
  • 지역 변수 접근은 전역 및 내장 함수 접근보다 훨씬 빠릅니다
  • . 연산자를 통한 멤버/메서드 접근도 느리므로, 이 비용을 피하려면 깊게 중첩된 값을 지역 변수에 다시 할당하는 것을 고려하십시오(예: h = a.b.c.d.e.f.g.h).
  • 상속된 메서드와 필드에 접근하는 것은 기반 클래스의 메서드와 필드에 접근하는 것보다 약 2배 느립니다. 가능하면 상속을 피하십시오.
  • 가능한 모든 곳에서 __slots__를 사용하십시오. __slots__는 클래스의 필드가 변경되지 않을 것임을 Python에 알리는 방법이며, 필드 접근 속도를 눈에 띄게 향상시킵니다. 이를 위해서는 필드 이름을 미리 지정하고 __init__에서 초기화해야 하지만, 얻는 이점에 비하면 작은 대가입니다.
  • match 문과 if..elif..else는 어떤 방식으로도 최적화되지 않습니다. 조건은 순서대로 하나씩 검사됩니다. 가능하다면 딕셔너리 디스패치나 값 테이블 같은 대안을 사용하십시오
  • 가능하면 지연 계산을 하십시오
  • 리스트 컴프리헨션은 일반적으로 루프보다 빠르며, 제너레이터 컴프리헨션은 리스트 컴프리헨션보다 약간 느리지만 메모리를 더 적게 사용합니다. 컴프리헨션은 Rust의 iter.map()에 해당한다고 생각할 수 있습니다. 리스트 컴프리헨션은 사실상 마지막에 collect::<Vec<_>>를 호출하는 반면, 제너레이터 컴프리헨션은 그렇지 않습니다.