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를 기반으로 대부분 순수 파이썬으로 구현했습니다.

저수준 부분: _rawffi

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

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

우리는 이 모듈을 가능한 한 작게 유지하려고 노력했습니다. 다른 구현체(예: Jython)가 _rawffi의 자체 버전을 작성하여 우리의 ctypes 구현을 사용할 수 있다는 것은 충분히 상상할 수 있는 일입니다.

고수준 부분들

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

논의 및 제한 사항

ctypes 기능을 재구현하는 것은 일반적으로 가능했습니다. PyPy는 교체 가능한 가비지 컬렉터를 지원하며, 그중 일부는 이동(moving) 컬렉터입니다. 이는 Python 객체 내부의 직접 참조를 외부 라이브러리로 전달하는 전략이 실현 가능하지 않음을 의미합니다(GC가 고정(pinning)을 지원하는 경우는 예외이지만, 현재는 그렇지 않습니다). 그 결과, 때로는 공유 대신 복사가 필요하며, 이는 일부 의미론적 차이를 일으킬 수 있습니다. _rawffi 자체로 생성된 C 객체는 GC 힙 외부에 할당되므로, 걱정 없이 외부 함수로 전달할 수 있습니다.

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

현재 구현의 제한 사항과 누락된 기능 목록은 다음과 같습니다:

  • ctypes.pythonapi가 없습니다. 이전 버전에서는 이것이 존재했으며 cpyext C API 에뮬레이션 레이어로 리디렉션되었지만, 저희 구현체는 GIL에 대해 합리적인 처리를 전혀 하지 않았고, 함수 이름에는 예를 들어 PyPyInt_FromLong()처럼 “Py”가 추가로 붙어 있었습니다. 도움이 되지 않는다는 이유로 제거되었습니다.
  • 원시(raw) 버퍼에 대한 포인터를 갖는 대신 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로 종료해야 합니다. 동작 방식에 대한 자세한 내용은 해당 docstring을 참조하십시오.

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

  • image_convert.py는 PIL이 필요합니다
  • image_display.py에는 PIL이 필요합니다
  • astraea/astraea.py는 PIL이 필요합니다

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

  • media_player.py는 avbin이 필요하거나, 최소한 .wav 파일을 위한 제대로 된 사운드 카드 설정이 필요합니다
  • video.py는 avbin이 필요합니다
  • soundscape는 avbin이 필요합니다