Back-End

24 set, 2013

Estendendo Python com C++

Publicidade

Senhoras e senhores, apresento-lhes esse casamento que sem sombra de dúvidas só tem como dar certo: C++ & Python. Neste artigo, vou explicar como estender a linguagem Python usando C++ e como instalar o mesmo usando setup.py.

Ai vocês me perguntam: Porque eu tenho que escrever trocentas linhas (eu disse trocentas linhas!) em C++ só para fazer um módulo, sendo que eu posso fazer isso com o próprio Python?

Bom, o motivo é bem simples e para terem a resposta, basta dar uma olhada nesse pequeno benchmark:

Caso de uso

Criando uma função calcular o número fatorial

Digamos que precisamos criar uma função que retorna o fatorial de um determinado número. Se usarmos um número pequeno, como 5, 20 ou 50, não haverá grandes problemas. Mas o que acontece se usarmo um número como 9999 (ou até mesmo maior)? Dependendo como você fizer ou de qual linguagem usar, você terá problemas.

É claro que estou superestimando o case, mas vamos imaginar que tratasse de um caso que necessite de alto desempenho e pouco processamento.

Então, vamos criar uma função que calcule o fatorial nas linguagens Python e C++. Depois criamos o mesmo módulo em C++ e o exportaremos para Python e compararemos os desempenhos de cada solução. Tentarei usar o melhor algoritmo possível para uma comparação justa e usarei pseudo-código para representá-la.

Função fat(número):
  Se número for igual a 1:
     Devolver número;
  Senão:
     Devolver número multiplicado por Função fat(número menos 1);

Bom, como dizia o Jack Estripador, vamos por partes! Começaremos o nosso teste implementando o algorítimo no Python.

Implementei o algoritmo para calcular o fatorial de 9999 e inclui algumas funções para fazer o cálculo do tempo de execução.

fat.py

#!/usr/bin/env python

import sys
import time

def fat(number):

	if(number==1):
		return number

	return number * fat( number - 1 )

def main():

	#Pega o tempo inicial
	t1 = time.time()

	#executa o fatorial
	fat(9999)

	#pega o tempo final
	t2 = time.time()

	#exibe o tempo de execucao
	print "tempo de execucao para calcular o "\
	"fatorial, foi de: ", (t2-t1)

if __name__=='__main__':
	sys.setrecursionlimit(999999)
	main()

O tempo de execução em Python foi de 0.0823390483856 segundos.

Agora faremos a mesma coisa com C++.

 fat.cpp

#include <cstdlib>
#include <iostream>
#include <time.h>

using namespace std;

/** Prototype */
long double fat(double number);

int main(){

	// Variáveis para o calculo do tempo de execução
	float tempo;
	time_t t_inicio,t_fim;

	// capturando o tempo inicial
	t_inicio = time(NULL);

	fat(9999);

	// capturando o tempo final
	t_fim = time(NULL);

	// verificando o intervalo
	tempo = difftime(t_fim,t_inicio);

	// Exibindo o tempo de execução
	cout << "O tempo de execução do ";
        cout << "programa foi de: " << tempo << endl;

	return 1;
}

long double fat(double number){
	if(number == 1)
		return number;
	return number * fat(number -1);
}

Após compilarmos e executarmos o programa, o seu tempo de execução em C++ foi de 0.0736501216888 segundos.

Bom, agora que a brincadeira começa. Vamos criar um arquivo chamado PyFat.cpp, incluir a função fat e registrá-la como módulo do Python.

PyFat.cpp

#include "/usr/include/python2.7/Python.h"

// função que calcula o fatorial
long double fat(double number){
	if(number == 1)
		return number;
	return number * fat(number -1);
}

/** Prototipo da função */
static PyObject *pyFat(PyObject *self, PyObject *args){

	double input;

	// Verifica se foi passado algum argumento pelo Python
	if(!PyArg_ParseTuple(args,"i",&input)){
		return 0;
	}

	return Py_BuildValue("i",fat(input));
}

// Resgistra a função
static PyMethodDef PrMethods[] = {
	{"fat",pyFat,METH_VARARGS,"Calcula fatorial"},
	{NULL, NULL, 0, NULL}
};
// Inicia a função initpr
PyMODINIT_FUNC
initpr(void){
	(void) Py_InitModule("pyFat",PrMethods);
}

Para testar, você pode compilar na mão ao invés de usar o setup.py basta fazer da seguinte forma:

root@server:~$ gcc -c PyFat.cpp -I/Python2.7/include
root@server:~$ gcc -shared hellomodule.o -L/Python2.7/libs 
-lpython2.7 -o pyfat.so

Agora que nosso arquivo PyFat.cpp está pronto, vamos criar o nosso arquivo setup.py para instalar o nosso módulo no Python.

#!/usr/bin/env python

from distutils.core import setup, Extension

module = Extension('fat', sources = ['PyFat.cpp'])
setup(name = 'Fatorial', 
      version = '1.0', 
      ext_modules = [module])

Vamos dar permissão de execução ao arquivo e  realizar o seguinte comando para a instalação do nosso módulo:

./setup.py install

Pronto, o nosso módulo está pronto para usar. Que tal testarmos o tempo de execução usando o nosso módulo fat?

Vamos criar o arquivo de teste. vou chamá-lo de FatPyTest.py.

import fat
import sys
import time

def main():

	#Pega o tempo inicial
	t1 = time.time()

	#executa o fatorial
	fat.fat(9999)

	#pega o tempo final
	t2 = time.time()

	#exibe o tempo de execucao
	print "tempo de execucao para calcular"\ 
        " o fatorial, foi de: ", (t2-t1)

if __name__=='__main__':
	sys.setrecursionlimit(999999)
	main()

Tempo de execução usando o módulo foi de 0.0740849971771 segundos. Tivemos um ganho de 0.0082540512085 segundos.

Claro que criar um módulos em C++ só para calcular o fatorial de um número não compensa, mas se pensarmos em rotinas que requerem grande processamento, com certeza valerá apena investir nessa solução. Lembrando que não só é possível estender a linguagem Python, essa técnica vale para quase todas as linguagens.

Caso queiram fazer download dos arquivos basta acessar a seguinte url: https://github.com/kmilotxm/post-estendendo-python-com-cpp

Bom, espero que tenham gostado da dica.

Referências