Nas últimas semanas tenho desenvolvido uma aplicação usando Cython e me surpreendi com o resultado.
Comecei usando o Cython apenas para compilar código Python – o que de fato rendeu o aumento de desempenho prometido –, mas então resolvi ir mais longe pra ver o quanto poderia extrair dessa plataforma: comecei a usar tipagem estática, funções C (
A cada novo passo era perceptível o crescimento do desempenho e da coerência geral do código. Minha única crítica é quanto aos testes: o código fica muito difícil de ser testado, já que não é possível fazer monkey-patch para colocar os mocks.
Segundo a documentação, ao compilar código Python com Cython, você tem um aumento de 35% no desempenho do código. Usando a tipagem estática, o aumento é de 300% e usando funções C e tipos de extensão o aumento é de aproximadamente 15.000%.
Não posso confirmar esses números, pois não fiz medições exatas, mas uma fila do RabbitMQ que enchia com 6, 7 mil mensagens, encheu com 64 mil no mesmo período de tempo (eu estava fazendo apenas carga, sem consumir a fila).
Uma coisa que gostei muito no Cython é o feeling: tem uma pegada bastante parecida com a do Objective C, o que faz sentido.
Vou dar um exemplo da própria documentação do Cython:
Dado o seguinte código Python puro:
Além disso, podemos adicionar tipagem ao código: Cython é um superset em Python, ou seja, é uma linguagem em si e um código Python é também código válido Cython.
No entanto ainda é possível otimizar mais ainda o código! Cython tem uma sintaxe específica que gera código C diretamente,
O
Por exemplo, da a seguinte classe:
O comando
Comecei usando o Cython apenas para compilar código Python – o que de fato rendeu o aumento de desempenho prometido –, mas então resolvi ir mais longe pra ver o quanto poderia extrair dessa plataforma: comecei a usar tipagem estática, funções C (
cdef
) e depois acabei migrando minhas classes para tipos de extensão (cdef class
es).A cada novo passo era perceptível o crescimento do desempenho e da coerência geral do código. Minha única crítica é quanto aos testes: o código fica muito difícil de ser testado, já que não é possível fazer monkey-patch para colocar os mocks.
Segundo a documentação, ao compilar código Python com Cython, você tem um aumento de 35% no desempenho do código. Usando a tipagem estática, o aumento é de 300% e usando funções C e tipos de extensão o aumento é de aproximadamente 15.000%.
Não posso confirmar esses números, pois não fiz medições exatas, mas uma fila do RabbitMQ que enchia com 6, 7 mil mensagens, encheu com 64 mil no mesmo período de tempo (eu estava fazendo apenas carga, sem consumir a fila).
Uma coisa que gostei muito no Cython é o feeling: tem uma pegada bastante parecida com a do Objective C, o que faz sentido.
Vou dar um exemplo da própria documentação do Cython:
Dado o seguinte código Python puro:
def f(x):
return x**2-x
def integrate_f(a, b, N):
s = 0
dx = (b-a)/N
for i in range(N):
s += f(a+i*dx)
return s * dx
Você pode salvar esse código em integrate.pyx
e compilá-lo assim:
bash$ cythonize -ib integrate.pyx
Isso irá gerar o código equivalente em C (integrate.c
) e compilá-lo na biblioteca integrate.so
, que pode ser diretamente importada em Python:
bash$ python
>>> from integrate import integrate_f
>>>
Se você tiver o IPython, pode usar o Cython no prompt:
bash$ ipython
Python 2.7.9 (default, Jan 7 2015, 11:49:12)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> %load_ext Cython
>>> %%cython
from integrate cimport integrate_f
De qualquer forma, só usando a biblioteca compilada em vez do código Python importado, Cython já promete um aumento de 35% de performance – o que não me frustrou.Além disso, podemos adicionar tipagem ao código: Cython é um superset em Python, ou seja, é uma linguagem em si e um código Python é também código válido Cython.
def f(double x):
return x**2-x
def integrate_f(double a, double b, int N):
cdef:
int i
double s, dx
s = 0
dx = (b-a)/N
for i in range(N):
s += f(a+i*dx)
return s * dx
Segundo a documentação, isso garante um desempenho 4 vezes superior ao de Python.No entanto ainda é possível otimizar mais ainda o código! Cython tem uma sintaxe específica que gera código C diretamente,
cdef
:
cdef double f(double x) except? -2:
return x**2-x
def integrate_f(double a, double b, int N):
cdef:
int i
double s, dx
s = 0
dx = (b-a)/N
for i in range(N):
s += f(a+i*dx)
return s * dx
O desempenho dessa função f
em C promete ser 150 vezes melhor do que a mesma função em Python puro.O
except? -2
na função é usado para passar exceções para o código C. Em um outro momento posso entrar em mais detalhes.Tipo de extensão
Tipos de extensão são equivalentes às classes de Python, porém mais restritos e muito mais eficientes.Por exemplo, da a seguinte classe:
class Consumer(object):
def __init__(self, backend):
self.backend = backend
def run(self, handler):
resp = self.backend.get()
return handler.handle(resp)
Considerando que as instâncias só serão executadas em Cython, o código equivalente estaria em dois arquivos, o primeiro consumer.pxd
:
from backends.base cimport Backend
from handlers.base cimport Handler
cdef class Consumer:
cdef:
Backend backend
int run(self, Handler handler) except? -1
Esse código .pxd
equivale ao cabeçalho .h
de C. Agora, o arquivo de implementação deve ser chamado consumer.pyx
:
from backends.base cimport Backend
from handlers.base cimport Handler
cdef class Consumer:
def __cinit__(self, Backend backend):
self.backend = backend
cdef int run(self, Handler handler) except? -1:
cdef dict resp = self.backend.get()
return handler.handle(resp)
Esse código promete ser muito mais eficiente que sua versão em Python, infelizmente métodos declarados como cdef
são acessíveis apenas em C (e em Cython). Para que o método seja acessível em Python, ele deve ser criado como um método Python comum (def
) ou pode ser usado cpdef
.O comando
cpdef
(C/Python def
) cria a função C (como cdef
) e um wrapper em Python para torná-la acessível. Se o módulo for importando com cimport
, será usada a função C original; se for importado com o clássico import
, será usado o wrapper.Conclusão
Até agora não tive motivos para me arrepender de usar Cython, recomendo.[]’s
ℭacilhας, La Batalema