(Конец) Учебные заметки 6: 91 предложение по улучшению программ на Python

Python
Заметки по изучению Python Сяобая, приглашаем всех прийти, чтобы сделать кирпич и топор.

Обновления предыдущих серий:

Если вы думаете, что я могу порекомендовать вам прочитать оригинальную книгу, и порекомендую вам две продвинутые книги по Python, которые я тоже читаю:

Вот текст:

Рекомендация 87: Воспользуйтесь наборами

Набор в Python — это неупорядоченный и неповторяющийся набор элементов, реализованный с помощью алгоритма Hash.

Проведем несколько тестов:

$ python -m timeit -n 1000 "[x for x in range(1000) if x in range(600, 1000)]"
1000 loops, best of 3: 6.44 msec per loop
$ python -m timeit -n 1000 "set(range(100)).intersection(range(60, 100))"   
1000 loops, best of 3: 9.18 usec per loop

На самом деле операции объединения, пересечения и разности множества выполняются быстрее, чем итерация списка. Поэтому, если речь идет о пересечении, объединении или различии списков, его можно преобразовать в набор для работы.

Рекомендация 88. Используйте многопроцессорность для преодоления недостатков GIL.

Multiprocess Multiprocess — это пакет управления несколькими процессами в Python, который был представлен в Python 2.6 и в основном используется для помощи в создании процессов, а также для связи и координации между ними. В основном он решает две проблемы: первая заключается в том, чтобы свести к минимуму различия между платформами и предоставить высокоуровневые API-интерфейсы, чтобы пользователи могли игнорировать основную проблему IPC, а другая — обеспечить совместную поддержку сложных объектов и поддержку локального и удаленного параллелизма.

Класс Process является более важным классом в многопроцессорности.Пользователь создает процесс, а его конструктор выглядит следующим образом:

Process([group[, target[, name[, args[, kwargs]]]]])

Среди них параметр target представляет вызываемый объект, args представляет кортеж позиционных параметров вызывающего объекта, kwargs представляет словарь вызывающего объекта, name — имя процесса, group обычно имеет значение None. Методы и атрибуты, предоставляемые этим классом, в основном такие же, как threading.Thread, включая is_alive(), join([timeout]), run(), start(), terminate(), демон (устанавливается start()), код выхода, имя, идентификатор и т. д.

В отличие от потоков, каждый процесс имеет свое адресное пространство, а пространство данных между процессами также не зависит друг от друга, поэтому совместное использование и передача данных между процессами не так удобна, как потоки. К счастью, модуль многопроцессорности предоставляет соответствующие механизмы: такие как примитивы операций межпроцессной синхронизации Lock, Event, Condition, Semaphore, традиционный механизм конвейерной связи pipe и queue Queue, multiprocess.Value и multiprocess.Array для общих ресурсов And Manager и так далее.

При использовании модуля Multiprocessing следует обратить внимание на следующие моменты:

  1. Связь между процессами отдает предпочтение Pipe и Queue, а не примитивам синхронизации, таким как Lock, Event, Condition и Semaphore. Внутрипроцессный класс Queue реализован с использованием каналов и некоторых блокировок, примитивов семафоров и безопасен для процесса. Конструктор этого класса возвращает общую очередь процесса, и его поддерживаемые методы в основном аналогичны методам Queue в потоках, за исключением того, что методы task_done() и join() реализованы в его подклассе JoinableQueue. Следует отметить, что поскольку нижний слой реализован с помощью пайпа, используйте Когда Queue обменивается данными между процессами, передаваемый объект должен быть сериализуемым, иначе операция put вызовет PicklingError. Кроме того, чтобы обеспечить контроль тайм-аута метода put, Queue не записывает объект напрямую в конвейер, а сначала записывает его в локальный кеш, а затем помещает в пайп из кеша. специальный нитеводитель внутри, который отвечает за эту работу. Из-за наличия фидера Queue также предоставляет следующие специальные методы для решения проблемы, связанной с наличием данных в кеше после выхода из процесса.

    • close(): указывает, что данные больше не хранятся в очереди. Как только все буферизованные данные будут сброшены в канал, фоновый поток завершится.

    • join_thread(): обычно используется после метода close, он блокируется до тех пор, пока не завершится фоновый поток, гарантируя, что все данные буфера будут сброшены в конвейер.

    • cancel_join_thread(): этот метод можно использовать, когда текущий процесс необходимо немедленно завершить, не дожидаясь сброса данных из очереди в базовый конвейер, что указывает на отсутствие необходимости предотвращать выход из фонового процесса.

    В Multiprocessing также есть очередь SimpleQueue, представляющая собой канал, реализующий механизм блокировки.Буфер удаляется внутри, но нет тайм-аута обработки для put и get.Оба действия являются блокирующими.

    В дополнение к multiprocessing.Queue другим важным методом связи является multiprocessing.Pipe. Его конструктором является multiprocess.Pipe([duplex]), где duplex по умолчанию имеет значение True, представляя двунаправленный канал, в противном случае он является однонаправленным. Он возвращает группу объектов Connection (conn1, conn2), представляющих каждый конец канала. Pipe не поддерживает безопасность процессов, поэтому возможна потеря или повреждение данных, когда несколько процессов одновременно считывают или записывают данные на один конец pipe. Поэтому, когда процесс обменивается данными, если он больше, чем Для более чем двух потоков можно использовать очереди, но производительность Pipe выше для связи между двумя процессами.

    from multiprocessing import Process, Pipe, Queue
    import time
    
    def reader_pipe(pipe):
        output_p, input_p = pipe    # 返回管道的两端
        inout_p.close()
        while True:
            try:
                msg = output_p.recv()    # 从 pipe 中读取消息
            except EOFError:
                    break
    
    def writer_pipe(count, input_p):    # 写消息到管道中
        for i in range(0, count):
            input_p.send(i)                # 发送消息
    
    def reader_queue(queue):            # 利用队列来发送消息
        while True:
            msg = queue.get()            # 从队列中获取元素
            if msg == "DONE":
                break
    
    def writer_queue(count, queue):
        for ii in range(0, count):
            queue.put(ii)                # 放入消息队列中
        queue.put("DONE")
    
    if __name__ == "__main__":
        print("testing for pipe:")
        for count in [10 ** 3, 10 ** 4, 10 ** 5]:
            output_p, input_p = Pipe()
            reader_p = Process(target=reader_pipe, args=((output_p, input_p),))
            reader_p.start()            # 启动进程
            output_p.close()
            _start = time.time()
            writer_pipe(count, input_p)    # 写消息到管道中
            input_p.close()
            reader_p.join()                # 等待进程处理完毕
            print("Sending {} numbers to Pipe() took {} seconds".format(count, (time.time() - _start)))
    
        print("testsing for queue:")
        for count in [10 ** 3, 10 ** 4, 10 ** 5]:
            queue = Queue()                # 利用 queue 进行通信
            reader_p = Process(target=reader_queue, args=((queue),))
            reader_p.daemon = True
            reader_p.start()
    
            _start = time.time()
            writer_queue(count, queue)    # 写消息到 queue 中
            reader_p.join()
            print("Seding {} numbers to Queue() took {} seconds".format(count, (time.time() - _start)))
    

    Приведенный выше код используется для проверки производительности использования канала и очереди для связи и отправки одних и тех же данных в случае двух многопотоков.Из вывода функции видно, что канал занимает меньше времени и имеет лучшую производительность.

  2. Старайтесь избегать совместного использования ресурсов. По сравнению с потоками накладные расходы на совместное использование ресурсов между процессами больше, поэтому старайтесь избегать совместного использования ресурсов. Но если это неизбежно, совместное использование памяти может быть достигнуто с помощью multiprocessing.Value и multiprocessing.Array или multiprocessing.sharedctpyes, а совместное использование данных и состояния также может быть достигнуто с помощью диспетчера серверных процессов Manager(). У этих двух методов есть свои преимущества: вообще говоря, метод с общей памятью быстрее и эффективнее, но диспетчер серверных процессов Manager() более удобен в использовании и поддерживает совместное использование локальной и удаленной памяти.

    # 示例一
    import time
    from multiprocessing import Process, Value
    
    def func(val):    # 多个进程同时修改 val
        for i in range(10):
            time.sleep(0.1)
            val.value += 1
    
    if __name__ == "__main__":
        v = Value("i", 0)    # 使用 value 来共享内存
        processList = [Process(target=func, args=(v,)) for i in range(10)]
        for p in processList: p.start()
        for p in processList: p.join()
        print v.value
    # 修改 func 函数,真正控制同步访问
    def func(val):
        for i in range(10):
            time.sleep(0.1)
            with val.get_lock():    # 仍然需要使用 get_lock 方法来获取锁对象
                val.value += 1
    # 示例二
    import multiprocessing
    def f(ns):
        ns.x.append(1)
        ns.y.append("a")
    
    if __name__ == "__main__":
        manager = multiprocessing.Manager()
        ns = manager.Namespace()
        ns.x = []    # manager 内部包括可变对象
        ns.y = []
    
        print("before process operation: {}".format(ns))
        p = multiprocessing.Process(target=f, args=(ns,))
        p.start()
        p.join()
        print("after process operation {}".format(ns))    # 修改根本不会生效
    # 修改
    import multiprocessing
    def f(ns, x, y):
        x.append(1)
        y.append("a")
        ns.x = x    # 将可变对象也作为参数传入
        ns.y = y
    
    if __name__ == "__main__":
        manager = multiprocessing.Manager()
        ns = manager.Namespace()
        ns.x = []    # manager 内部包括可变对象
        ns.y = []
    
        print("before process operation: {}".format(ns))
        p = multiprocessing.Process(target=f, args=(ns, ns.x, ns.y))
        p.start()
        p.join()
        print("after process operation {}".format(ns))
    
  3. Помните о различиях между платформами. Поскольку платформа Linux использует fork() для создания процесса, все ресурсы в родительском процессе, такие как структуры данных, открытые файлы или соединения с базой данных, будут общими для дочернего процесса, в то время как родительский и дочерний процессы на платформе Windows относительно независимы, поэтому, чтобы лучше поддерживать совместимость с платформой, лучше всего передать соответствующий объект ресурса в качестве аргумента конструктору дочернего процесса. Чтобы избежать следующих способов:

    f = None
    def child(f):
        # do something
    
    if __name__ == "__main__":
        f = open(filename, mode)
        p = Process(target=child)
        p.start()
        p.join()
    # 推荐的方式
    def child(f):
        print(f)
    
    if __name__ == "__main__":
        f = open(filename, mode)
        p = Process(target=child, args=(f, ))    # 将资源对象作为构造函数参数传入
        p.start()
        p.join()
    

    Следует отметить, что реализация многопроцессорности на платформе Linux основана на fork() в библиотеке C, а данные всех дочерних процессов точно такие же, как и у родительского процесса, поэтому все ресурсы в родительском процессе, такие как структуры данных, открытые файлы или базы данных. Соединения являются общими для дочерних процессов. Однако, поскольку на платформе Windows нет функции fork(), родительский и дочерний процессы относительно независимы, поэтому поддерживается совместимость с платформой. script, чтобы избежать RuntimeError или взаимоблокировки.

  4. Старайтесь избегать использования функции terminate() для завершения процесса и убедитесь, что параметры, переданные в pool.map, сериализуемы.

    import multiprocessing
    def unwrap_self_f(*args, **kwargs):
        return calculate.f(*args, **kwargs)    # 返回一个对象
    
    class calculate(object):
        def f(self, x):
            return x * x
        def run(self):
            p = multiprocessing.Pool()
            return p.map(unwrap_self_f, zip([self] * 3, [1, 2, 3]))
    
    if __name__ == "__main__":
        c1 = calculate()
        print(c1.run())
    

Рекомендация 89. Используйте пулы потоков для повышения эффективности.

Мы знаем, что жизненный цикл потока делится на 5 состояний: создан, готов, запущен, заблокирован и завершен. От создания потока до его завершения поток непрерывно переходит между тремя состояниями выполнения, готовности и блокировки, пока не будет уничтожен. Единственные три состояния, которые действительно занимают ЦП, — это работа, создание и уничтожение. Таким образом, время выполнения потока можно разделить на три части: время запуска потока (Ts), время выполнения тела потока (Tr) и время уничтожения потока (Td). В контексте многопоточности, если поток нельзя использовать повторно, это означает, что каждое создание должно пройти через три процесса запуска, уничтожения и выполнения. Это неизбежно увеличит время отклика системы и снизит эффективность. Время выполнения тела потока Tr не поддается контролю, и в этом случае для повышения эффективности работы потока решением является пул потоков.

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

Так как поток создается заранее и помещается в пул потоков, а после обработки текущей задачи он не уничтожается, а организуется для обработки следующей задачи, то можно избежать многократного создания потоков, тем самым экономя накладные расходы создание и уничтожение потоков и получение дополнительных преимуществ Хорошая производительность и стабильность системы. Технология пула потоков подходит для обработки большого количества внезапных запросов или требует большого количества потоков для выполнения задач, но фактическое время обработки задачи короткое Это может эффективно избежать чрезмерной нагрузки на производительность системы и ответов, вызванных слишком большим количеством потоков. потоки, созданные в системе, слишком медленные и т. д.

Есть два решения для использования пулов потоков в Python: одно — реализовать режим пула потоков самостоятельно, а другое — использовать модуль пула потоков.

Давайте сначала рассмотрим простую реализацию шаблона пула потоков:код пула потоков

Этот абзац кода слишком длинный, и после редактирования его нельзя сохранить на Zhihu, поэтому я должен опубликовать адрес своего блога. Вы можете перейти в блог, чтобы посмотреть его.

Чтобы реализовать потоки самостоятельно, вам необходимо определить Worker для обработки рабочих запросов и определить WorkerManager для управления и создания пулов потоков. Он включает в себя очередь рабочих запросов и очередь результатов выполнения. Конкретная работа по загрузке реализуется методом download_file .

Зачастую проще использовать готовый модуль пула потоков, чем самостоятельно реализовать модель пула потоков. Модуль пула потоков в Pythonссылка на скачивание. Этот модуль предоставляет следующие основные классы и методы:

  • threadpool.ThreadPool: класс пула потоков в основном используется для отправки запросов задач и сбора текущих результатов. В основном это следующие методы:

    • __init__(self, num_workers, q_size=0, resq_size=0, poll_timeout=5): создайте пул потоков и запустите поток, соответствующий num_workers; q_size представляет размер очереди запросов задач, а resq_size представляет размер очереди для сохранение текущих результатов.

    • createWorkers(self, num_workers, poll_timeout=5): добавить потоки, соответствующие количеству num_workers, в пул потоков.

    • rejectWorkers(self, num_workers, do_join=False): указать num_workers количество рабочих потоков, которые необходимо закрыть после выполнения текущей задачи.

    • joinAllDismissedWorkers(self): выполнить Thread.join в потоке, установленном для выхода

    • putRequest(self, request, block=True, timeout=None): поставить рабочий запрос в очередь

    • poll(self, block=False): обрабатывать новые запросы в очереди задач. wait(self): Block используется для ожидания всех результатов выполнения. Обратите внимание, что когда возвращаются все результаты выполнения, потоки внутри пула потоков не уничтожаются, а ожидают новых задач. Следовательно, после wait() вы все еще можете снова вызвать pool.putRequests(), чтобы добавить в него задачи.

  • threadpool.WorkRequest: класс рабочего запроса, который содержит определенные методы выполнения.

  • threadpool.WorkerThread: рабочий поток, который обрабатывает задачи, в основном включая метод run() и метод отклонения().

  • makeRequests(callable_, args_list, callback=None, exec_callback=_handle_thread_exception): основная функция, роль которой заключается в создании серии рабочих запросов с одной и той же функцией выполнения, но с разными параметрами.

Давайте рассмотрим пример реализации пула потоков:

import urllib2
import os
import time
import threadpool

def download_file(url):
    print("begin download {}".format(url ))
    urlhandler = urllib2.urlopen(url)
    fname = os.path.basename(url) + ".html"
    with open(fname, "wb") as f:
        while True:
            chunk = urlhandler.read(1024)
            if not chunk:
                break
            f.write(chunk)

urls = ["http://wiki.python.org/moni/WebProgramming",
       "https://www.createspace.com/3611970",
       "http://wiki.python.org/moin/Documention"]
pool_size = 2
pool = threadpool.ThreadPool(pool_size)    # 创建线程池,大小为 2
requests = threadpool.makrRequests(download_file, urls)    # 创建工作请求
[pool.putRequest(req) for req in requests]

print("putting request to pool")
pool.putRequest(threadpool.WorkRequest(download_file, args=["http://chrisarndt.de/projects/threadpool/api/",]))    # 将具体的请求放入线程池
pool.putRequest(threadpool.WorkRequest(download_file, args=["https://pypi.python.org/pypi/threadpool",]))
pool.poll()    # 处理任务队列中的新的请求
pool.wait()
print("destory all threads before exist")
pool.dismissWorkers(pool_size, do_join=True)    # 完成后退出

Рекомендация 90. Повышение производительности с помощью модулей C/C++.

Python обладает хорошей расширяемостью.Использование предоставляемых Python API, таких как макросы, типы, функции и т. д., позволяет Python легко расширять C/C++ для повышения производительности выполнения. Все эти API содержатся в заголовочном файле Python.h, который можно импортировать при написании кода на C.

Давайте посмотрим на простой пример:

1. Сначала используйте C для реализации связанных функций для реализации оценки простых чисел, или вы можете напрямую использовать язык C для реализации связанных функций, а затем использовать Python для упаковки.

#include "Python.h"
static PyObject * pr_isprime(PyObject, *self, PyObject * args) {
  int n, num;
  if (!PyArg_ParseTuple(args, "i", &num))    // 解析参数
    return NULL;
  if (num < 1) {
    return Py_BuildValue("i", 0);    // C 类型的数据结构转换成 Python 对象
  }
  n = num - 1;
  while (n > 1) {
    if (num % n == 0) {
      return Py_BuildValue("i", 0);
      n--;
    }
  }
  return Py_BuildValue("i", 1);
}

static PyMethodDef PrMethods[] = {
  {"isPrime", pr_isprime, METH_VARARGS, "check if an input number is prime or not."},
  {NULL, NULL, 0, NULL}
};

void initpr(void) {
  (void) Py_InitModule("pr", PrMethods);
}

Приведенный выше код состоит из следующих 3 частей:

  • Функция экспорта: интерфейсная функция pr_isprime, предоставляемая модулем C, имеет два параметра: self и args. Параметр args содержит все параметры, которые интерпретатор Python хочет передать функции C. Обычно для получения используется функция PyArg_ParseTuple(). эти значения параметров.

  • Функция инициализации: чтобы интерпретатор Python мог правильно инициализировать модуль, начните с init, например, initp

  • Список методов: таблица сопоставления имен функций модуля C PrMethods, предоставляемая внешним программам Python. Это структура PyMethodDef, в которой элементы представляют имя метода, экспортируемую функцию, метод передачи параметров и описание метода, в свою очередь. См. пример ниже:

 struct PyMethodDef {
    char * m1_name;        // 方法名
    PyCFunction m1_meth;    // 导出函数
    int m1_flags;            // 参数传递方法
    char * m1_doc;        // 方法描述
 }

Метод передачи параметров обычно имеет значение METH_VARARGS. Если вы хотите передать параметры ключевого слова, вы можете использовать ИЛИ с METH_KEYWORDS. Если вы не хотите принимать какие-либо аргументы, вы можете установить значение METH_NOARGS. Структура должна заканчиваться пустой записью, представленной {NULL, NULL, 0, NULL}.

2. Напишите сценарий setup.py.

from distutils.core import setup, Extension
module = Extension("pr", sources=["testextend.c"])
setup(name="Pr test", version="1.0", ext_modules=[module])

3. Используйте python setup.py build для компиляции, система создаст подкаталог сборки в текущем каталоге, который содержит файлы pr.so и pr.o.

4. Скопируйте сгенерированный файл py.so в каталог Python site_packages или добавьте путь к каталогу, в котором находится pr.so, в sys.path, и вы сможете использовать расширенный модуль C.

Подробнее о расширениях модуля CСсылаться на.

Рекомендация 91: Пишите модули расширения на Cython

Python-API упрощает написание модулей расширения на C/C++ для повышения производительности за счет перезаписи кода узких мест в вашем приложении. Тем не менее, есть еще несколько проблем с этим подходом:

  1. Овладение языком программирования C/C++ и цепочкой инструментов требует огромных затрат на обучение.

  2. Даже если вы хорошо владеете C/C++, потребуется много работы по переписыванию кода, например, написанию версий C/C++ определенных структур данных и алгоритмов, что отнимает много времени и чревато ошибками.

Поэтому все сообщество Python усердно работает над созданием «компилятора», который может напрямую компилировать код Python в эквивалентный код C/C++, что приводит к повышению производительности, например Pyrex, Py2C и Cython. И Cython, который был разработан на основе Pyrex, является мастером в этом.

Cython обеспечивает очень высокую эффективность выполнения кода C, преобразованного из кода Python, за счет добавления объявлений типов в код Python и прямого вызова функций C. Его преимущество в том, что он поддерживает почти все возможности Python, то есть в основном весь код Python является валидным кодом Cython, что минимизирует затраты на внедрение технологии Cython в проект. Кроме того, Cython поддерживает использование синтаксиса декоратора для объявления типов и даже поддерживает специальные файлы объявления типов, так что исходный код Python может продолжать оставаться независимым.Эти функции делают его широко используемым, например Такие библиотеки, как PyAMF, PyYAML и т. д., используют его для написания собственных эффективных версий.

# 安装
$ pip install -U cython
# 生成 .c 文件
$ cython arithmetic.py
# 提交编译器
$ gcc -shared -pthread -fPIC -fwrapv -02 -Wall -fno-strict-aliasing -I /usr/include/python2.7 -o arithmetic.so arithmetic.c
# 这时生成了 arithmetic.so 文件
# 我们就可以像 import 普通模块一样使用它

Каждый раз компилировать и ждать несколько обременительно, поэтому Cython очень внимательно предлагает решение без явной компиляции: pyximport. Просто измените суффикс исходного кода Python с .py на .pyx.

$ cp arithmetic.py arithmetic.pyx
$ cd ~
$ python
>>> import pyximport; pyximport.install()
>>> import arithmetic
>>> arithmetic.__file__

Как видно из атрибута __file__, этот файл .pyx был скомпилирован и связан как общая библиотека, и pyximport действительно удобен!

Далее давайте посмотрим, как Cython повышает производительность.

В ГИС часто бывает необходимо рассчитать расстояние между двумя точками на земной поверхности:

import math
def great_circle(lon1, lat1, lon2, lat2):
    radius = 3956    # miles
    x = math.pi / 180.0
    a = (90.0 - lat1) * (x)
    b = (90.0 - lat2) * (x)
    theta = (lon2 - lon1) * (x)
    c = math.acos(math.cos(a) * math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta))
    return radius * c

Затем попробуйте Cython переписать:

import math
def great_circle(float lon1, float lat1, float lon2, float lat2):
    cdef float radius = 3956.0
    cdef float pi = 3.14159265
    cdef float x = pi / 180.0
    cdef float a, b, theta, c
    a = (90.0 - lat1) * (x)
    b = (90.0 - lat2) * (x)
    theta = (lon2 - lon1) * (x)
    c = math.acos(math.cos(a) * math.cos(b)) + (math.sin(a) * math.sin(b) * math.cos(theta))
    return radius * c

Добавляя объявления типов к параметрам и промежуточным переменным функции great_circle, код бизнес-логики кода Cython остается неизменным. Используя библиотеку timeit, можно измерить ускорение почти на 20%, что указывает на то, что объявления типов очень полезны для повышения производительности. В настоящее время все еще существует узкое место в производительности.Вызываемая математическая библиотека является библиотекой Python с низкой производительностью.Это можно решить, напрямую вызвав функцию C:

cdef extern from "math.h":
    float cosf(float theta)
    float sinf(float theta)
    float acosf(float theta)

def greate_circle(float lon1, float lat1, float lon2, float lat2):
    cdef float radius = 3956.0
    cdef float pi = 3.14159265
    cdef float x = pi / 180.0
    cdef float a, b, theta, c
    a = (90.0 - lat1) * (x)
    b = (90.0 - lat2) * (x)
    theta = (lon2 - lon1) * (x)
    c = acosf((cosf(a) * cosf(b)) + (sinf(a) * sinf(b) * cosf(theta)))
    return radius * c

Cython использует синтаксис cdef extern from для импорта в код таких функций, как cofs, sinf и acosf, объявленных в заголовочном файле библиотеки языка C math.h. Тестирование этой версии кода с помощью timeit улучшило производительность кода более чем в 5 раз за счет сокращения вызовов функций Python и накладных расходов на преобразование типов, возникающих во время вызова.

В этом примере вы можете освоить два навыка Cython: объявления типов и прямой вызов функций C. Использование подхода Cython намного удобнее, чем написание модулей расширения непосредственно на C/C++.

Помимо использования Cython для написания модулей расширения для повышения производительности, Cython также можно использовать для инкапсуляции ранее написанного кода C/C++ в модули .so для вызовов Python (аналогично boost.python/SWIG). много автоматики.инструмент.