Série de posts sobre Cython:
- Introdução ao desenvolvimento usando Cython
- Distribuição de extensões em Cython
- Análise de eficiência de operações
- Paralelismo e NoGIL
No texto anterior apresentei o básico da utilização de Cython dentro do iPython Notebook. No segundo texto desta série exploramos maneiras de distribuir código Cython que pode ser importado como qualquer outro módulo escrito em Python.
Preparação¶
Vamos agora disponibilizar nossa função de detecção de bordas (que fizemos na parte 1) para utilização em outros scripts. Para isto, salve o seguinte código como gradiente.pyx. Note que usamos a extensão pyx para o arquivo para indicar que ele contém código em Cython.
# file: gradiente.pyx
import cython
import numpy as np
@cython.boundscheck(False)
@cython.nonecheck(False)
def morpho_gradient_cython(long[:,:] img, long[:,:] out, p):
cdef int i, j, pi, pj, minv, maxv
pi, pj = p
minv = 255
maxv = 0
for i in range(-1, 2):
for j in range(-1, 2):
if img[pi+i, pj+j] < minv:
minv = img[pi+i, pj+j]
if img[pi+i, pj+j] > maxv:
maxv = img[pi+i, pj+j]
out[pi, pj] = maxv - minv
def gradiente_cython(long[:,:] img):
cdef int i, j, w, h
cdef long[:,:] out
h = img.shape[0]; w = img.shape[1]
out = np.zeros((h, w), np.int)
for i in range(1, h-1):
for j in range(1, w-1):
morpho_gradient_cython(img, out, (i, j))
return out
Opção 1: pyximport¶
A maneira mais fácil de se importar um módulo em Cython é usar o módulo pyximport
. Quando fazemos um import XXX
, o pyximport procura por arquivos .pyx e, se houver um XXX.pyx, o compila automaticamente e o importa. Se o módulo já tiver sido compilado anteriormente e não houveram mudanças ele é importado diretamente.
#file: teste_import1.py
import pyximport
pyximport.install()
import numpy as np
import scipy as sp
import scipy.misc
import gradiente
img = sp.misc.lena()
out = gradiente.gradiente_cython(img)
sp.misc.imsave('result.png', out)
Apesar do pyximport ser muito conveniente, ele é adequado somente para scripts menores e que cabem inteiros em um só arquivo. Além disto, os fontes são compilados na primeira utilização e, dependendo do caso, isto pode demorar um bocado. Por outro lado, para testes rápidos ou scripts pequenos o pyximport é muito prático e adequado.
Nota Windows: se você está usando mingw
para compilar extensões no Windows, pode ser necessário adicionar o argumento setup_args={'options': {'build_ext': {'compiler': 'mingw32'}}}
na segunda linha para forçar a utilização do mingw
.
Nota 2: recentemente tenho tido problemas com o pyximport pegando o Python errado
Opção 2: distutils¶
Projetos mais complexos com Cython devem utilizar distutils
para gerenciar a compilação dos fontes em Cython (e também de outros fontes em C usados no projeto). A configuração fica um pouco mais complexa devido à criação de um setup.py
, porém todos os fontes são compilados antes da importação do módulo e é possível inclusive criar pacotes binários para utilização em sistemas em que não é fácil compilar fontes (Windows, basicamente).
Veja abaixo um setup.py
básico para compilar módulos em Cython.
#file: setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy as np
pyx_files = [Extension('gradiente', ['gradiente.pyx']) # para cada arquivo colocamos o nome do modulo desejado e o arquivo pyx.
setup(ext_modules=cythonize(pyx_files))
Podemos, então, compilar nosso código em Cython executando
$ python setup.py build_ext --inplace
A instrução --inplace
é usada para compilar o módulo na pasta atual. Se ela não é usada, o módulo compilado fica disponível na pasta build/
. Com o código compilado, podemos importar nosso módulo normalmente em scripts.
#file: teste-import2.py
import numpy as np
import scipy as sp
import scipy.misc
import gradiente
img = sp.misc.lena()
out = gradiente.gradiente_cython(img)
sp.misc.imsave('result.png', out)
Opção 3: compilação manual¶
Esta opção não é recomendada, mas pode ser usada eventualmente para que possamos inspecionar o código C gerado. O módulo distutils
executa estes passos automaticamente e verifica se os arquivos mudaram, compilando somente as partes necessárias. Em geral, é melhor usar distutils
do que compilar manualmente.
O seguinte comando gera um arquivo gradiente.c
que contém o resultado da "tradução" feita pelo Cython. Como na utilização em notebooks, a opção -a
pode ser usada para gerar um relatório que mostra a utilização do interpretador pelo código gerado.
$cython gradiente.pyx
Então, compilamos a extensão usando
$ gcc -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -shared -I(caminho_includes_python) -o gradiente.(so|pyd) gradiente.c
No anaconda python, o caminho usado é anaconda_dir/include/pythonX.Ym
, onde X.Y
é a sua versão do python instalada.
Com isto, vimos como utilizar código em Cython não só no iPython notebook, mas também em qualquer script Python. Nos próximos textos vamos explorar mais a relação entre o Cython e o interpretador, focando em criar programas que explicitamente liberam o interpretador Python. Até lá!
Nenhum comentário:
Postar um comentário