Лучшие практики, соглашения и знания Cython#

Этот документ содержит советы по разработке кода на Cython в scikit-learn.

Советы по разработке с использованием Cython в scikit-learn#

Советы для облегчения разработки#

  • Время, затраченное на чтение Документация Cython не является потерянным временем.

  • Если вы планируете использовать OpenMP: В MacOS, системное распространение clang не реализует OpenMP. Вы можете установить compilers пакет доступен на conda-forge который поставляется с реализацией OpenMP.

  • Активация проверки может помочь. Например, для активации boundscheck используйте:

    export SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES=1
    
  • Начните с нуля в блокноте чтобы понять, как использовать Cython и быстро получить обратную связь по вашей работе. Если вы планируете использовать OpenMP для ваших реализаций в вашем Jupyter Notebook, добавьте дополнительные аргументы компилятора и линкера в магии Cython.

    # For GCC and for clang
    %%cython --compile-args=-fopenmp --link-args=-fopenmp
    # For Microsoft's compilers
    %%cython --compile-args=/openmp --link-args=/openmp
    
  • Для отладки C-кода (например, segmentation fault) используйте gdb с:

    gdb --ex r --args python ./entrypoint_to_bug_reproducer.py
    
  • Чтобы иметь доступ к некоторому значению для отладки в cdef (nogil) контексте, используйте:

    with gil:
        print(state_to_print)
    
  • Обратите внимание, что Cython не может обрабатывать f-строки с {var=} выражения, например,

    print(f"{test_val=}")
    
  • Кодовая база scikit-learn содержит множество не унифицированных (слитых) определений типов. В настоящее время существует текущая работа по упрощению и унификации этого в кодовой базе. Пока убедитесь, что вы понимаете, какие конкретные типы используются в конечном итоге.

  • Этот псевдоним может быть удобен для компиляции отдельных расширений Cython:

    # You might want to add this alias to your shell script config.
    alias cythonX="cython -X language_level=3 -X boundscheck=False -X wraparound=False -X initializedcheck=False -X nonecheck=False -X cdivision=True"
    
    # This generates `source.c` as if you had recompiled scikit-learn entirely.
    cythonX --annotate source.pyx
    
  • Используя --annotate опция с этим флагом позволяет генерировать HTML-отчет аннотации кода. Этот отчет указывает взаимодействия с интерпретатором CPython построчно. Взаимодействия с интерпретатором CPython должны быть максимально избегаемы в вычислительно интенсивных разделах алгоритмов. Для получения дополнительной информации обратитесь к этот раздел руководства по Cython

    # This generates a HTML report (`source.html`) for `source.c`.
    cythonX --annotate source.pyx
    

Советы по производительности#

  • Понять GIL в контексте CPython (какие проблемы он решает, каковы его ограничения) и получить хорошее понимание того, когда Cython будет преобразован в код C без взаимодействия с CPython, когда не будет, и когда не может (например, наличие взаимодействий с объектами Python, которые включают функции). В этом отношении, PEP073 предоставляет хороший обзор, контекст и пути для удаления.

  • Убедитесь, что вы отключили проверки.

  • Всегда предпочитайте memoryviews вместо cnp.ndarray когда возможно: memoryviews являются легковесными.

  • Избегайте срезов memoryview: срезы memoryview могут быть затратными или вводящими в заблуждение в некоторых случаях, и лучше не использовать их, даже если обработка меньшего количества измерений в некоторых контекстах была бы предпочтительнее.

  • Декорируйте финальные классы или методы с помощью @final (это позволяет удалять виртуальные таблицы при необходимости)

  • Встраивайте методы и функции, когда это имеет смысл

  • В сомнении, прочитайте сгенерированный код на C или C++, если можете: "Чем меньше инструкций C и косвенных обращений для строки кода на Cython, тем лучше" — хорошее правило.

  • nogil объявления являются лишь подсказками: при объявлении cdef функции как nogil означают, что их можно вызывать без удержания GIL, но они не освобождают GIL при входе в них. Вы должны сделать это самостоятельно, либо передав nogil=True to cython.parallel.prange явно или с использованием явного контекстного менеджера:

    cdef inline void my_func(self) nogil:
    
        # Some logic interacting with CPython, e.g. allocating arrays via NumPy.
    
        with nogil:
            # The code here is run as if it were written in C.
    
        return 0
    

    Этот пункт основан на этот комментарий от Stéfan’s Benhel

  • Прямые вызовы подпрограмм BLAS возможны через интерфейсы, определенные в sklearn.utils._cython_blas.

Использование OpenMP#

Поскольку scikit-learn может быть собран без OpenMP, необходимо защищать каждый прямой вызов OpenMP.

The _openmp_helpers модуль, доступный в sklearn/utils/_openmp_helpers.pyx предоставляет защищенные версии процедур OpenMP. Чтобы использовать процедуры OpenMP, они должны быть cimported из этого модуля, а не из библиотеки OpenMP напрямую:

from sklearn.utils._openmp_helpers cimport omp_get_max_threads
max_threads = omp_get_max_threads()

Параллельный цикл, prange, уже защищена cython и может использоваться напрямую из cython.parallel.

Типы#

Код на Cython требует использования явных типов. Это одна из причин, по которой вы получаете прирост производительности. Чтобы избежать дублирования кода, у нас есть центральное место для наиболее часто используемых типов в sklearn/utils/_typedefs.pxd. В идеале вы начинаете с просмотра там и cimport типы, которые вам нужны, например

from sklearn.utils._typedefs cimport float32, float64