PyPy의 ctypes 구현

요약

용어:

  • 애플리케이션 수준 코드 - 완전한 Python으로 작성된 코드
  • 인터프리터 수준 코드 - RPython으로 작성되어 C 같은 다른 언어로 컴파일되는 코드로, 인터프리터의 일부입니다.

현재 상태의 PyPy ctypes 구현은 CPython용 ctypes와 동일한 인터페이스와 동작을 지닌 모듈을 PyPy용으로 구현할 수 있음을 입증합니다.

PyPy의 구현은 CPython의 ctypes와 마찬가지로 내부적으로 libffi를 사용합니다. 저희 구현에서는 가능한 한 많은 코드를 RPython이 아닌 순수 Python으로 작성했습니다. CPython의 경우로 말하자면, 이는 C로 작성하는 코드를 최소한으로 줄이는 것에 해당합니다. 이번 첫 시험 구현에서는 기본적으로 속도를 걱정하기보다 빠른 실험을 우선시했습니다. 그 덕분에 실제 기간으로 2개월 만에 ctypes 기능의 상당 부분을 갖춘 동작하는 구현을 제공할 수 있었습니다.

저희는 CPython의 ctypes 패키지 버전 1.0.2를 그대로 재사용했습니다. CPython에서는 C 모듈인 _ctypes를 저희는 하위 수준 계층의 확장 모듈인 _rawffi에 기반하여 대부분 순수 Python으로 구현했습니다.

저수준 부분: _rawffi

이 PyPy 확장 모듈(pypy/module/_rawffi)은 libffi를 통해 C 객체(배열과 구조체)를 생성하고 동적 라이브러리의 함수를 호출할 수 있는 간단한 인터페이스를 제공합니다. 대부분의 경우 객체를 해제하는 일과 서로를 참조하는 객체들이 계속 살아 있도록 보장하는 일은 상위 계층의 책임입니다.

이 모듈은 rpython/rlib/libffi.py에 정의된 libffi 바인딩을 사용합니다.

저희는 이 모듈을 가능한 한 작게 유지하려고 했습니다. 다른 구현체(예: Jython)도 자체 버전의 _rawffi를 작성하면 저희 ctypes 구현을 사용할 수 있을 것으로 생각합니다.

고수준 구성 요소

재사용되는 ctypes 패키지는 lib_pypy/ctypes에 있습니다. CPython의 _ctypes와 동일한 인터페이스를 구현하는 _ctypes는 lib_pypy/_ctypes에 있습니다.

논의 및 한계

ctypes 기능을 다시 구현하는 것은 대체로 가능했습니다. PyPy는 교체 가능한 가비지 컬렉터를 지원하며, 그중 일부는 객체를 이동시키는 컬렉터입니다. 따라서 Python 객체 내부를 직접 가리키는 참조를 외부 라이브러리에 전달하는 방식은 사용할 수 없습니다(GC가 고정(pinning)을 지원한다면 가능하지만, 현재는 지원하지 않습니다). 그 결과 때로는 공유 대신 복사가 필요하며, 이로 인해 의미론에 일부 차이가 생길 수 있습니다. _rawffi 자체로 생성한 C 객체는 GC 힙 바깥에 할당되므로, 걱정 없이 외부 함수에 전달할 수 있습니다.

구현을 인터프리터 수준으로 이식하면 속도가 향상될 가능성이 높습니다. 게다가 현재의 계층 구조와 현재의 _rawffi 인터페이스는 꼭 필요한 것보다 더 많은 객체 할당과 복사를 요구합니다. 이 부분도 개선할 수 있습니다.

현재 구현의 제약 사항과 아직 지원하지 않는 기능은 다음과 같습니다.

  • ctypes.pythonapi가 없습니다. 이전 버전에는 이것이 있었고 cpyext C API 에뮬레이션 계층으로 리디렉션되었지만, 저희 구현은 GIL을 전혀 제대로 처리하지 못했으며 함수 이름에도 PyPyInt_FromLong()처럼 “Py”가 하나 더 붙어 있었습니다. 도움이 되지 않았기 때문에 제거되었습니다.
  • 원시 버퍼를 가리키는 포인터를 두는 대신 Python 문자열을 복사합니다
  • 구현하지 못한 기능은 다음과 같습니다:
    • 사용자 정의 정렬 및 비트 필드
    • 크기 조정(resize() 함수)
    • 네이티브가 아닌 바이트 순서를 사용하는 객체
    • 값으로 전달되는 구조체를 인자로 받는 콜백
    • ctypes가 자신의 기본 타입과 사용자가 정의한 기본 타입의 서브클래스 사이에 두는 미묘한 의미론 차이

애플리케이션 예제 실행하기

pyglet은 실행되는 것으로 알려져 있습니다. 또한 pygame-ctypes(더 이상 유지 보수되지 않음)와 실험적인 pysqlite-ctypes의 스냅숏으로도 어느 정도 성과를 거두었습니다. 여기서는 pyglet 예제를 실행하는 방법만 설명합니다.

pyglet

pyglet은 저장소에서 리비전 1984를 체크아웃하여 시험해 보았습니다.

pyglet 예제 중 다음 예제들이 동작하는 것으로 알려져 있습니다:

  • opengl.py
  • multiple_windows.py
  • events.py
  • html_label.py
  • timer.py
  • window_platform_event.py
  • fixed_resolution.py

ctypes 테스트를 실행하기 위해 변환한 pypy-c로 pyglet 예제도 실행할 수 있습니다. 예를 들어 다음과 같이 실행할 수 있습니다.:

$ cd pyglet/
$ PYTHONPATH=. ../ctypes-stable/pypy/goal/pypy-c examples/opengl.py

보통 ctrl-c로 종료해야 합니다. 동작 방식에 대한 자세한 내용은 해당 문서 문자열을 참조하십시오.

다음 예제들은 ctypes와 무관한 이유로 동작하지 않습니다:

  • image_convert.py를 실행하려면 PIL이 필요합니다
  • image_display.py에는 PIL이 필요합니다
  • astraea/astraea.py를 실행하려면 PIL이 필요합니다

다음 예제들은 시도해 보지 않았습니다:

  • media_player.py에는 avbin이 필요하며, 그렇지 않다면 최소한 .wav 파일용 사운드 카드가 제대로 설정되어 있어야 합니다
  • video.py를 사용하려면 avbin이 필요합니다
  • soundscape에는 avbin이 필요합니다