segunda-feira, 14 de outubro de 2013

Meu primeiro aplicativo Android

Publiquei essa semana no Google Play meu primeiro aplicativo Android. O 9Gag Offline trata-se de um aplicativo que permite que você baixe os gags do site 9Gag para momentos em que não haja conexão com internet. Por exemplo se você for viajar, no avião você ficará isolado do mundo mas poderá rir bastante com os gags que estarão guardados no seu celular.

Já fazia mais ou menos uns seis meses que estava estudando esporadicamente o sistema operacional da Google e fazendo diversos testes. A um mês atrás resolvi fechar de vez o aplicativo e colocá-lo ao público. Deu pra aprender bastante sobre o framework e reviver os problemas de sincronia e concorrência que haviam na época em que programava para desktop com Delphi, Kylix e Java Swing.

Espero que gostem e se acharem qualquer problema ou tiverem alguma sugestão é só avisar. Para baixar o 9Gag Offline basta clicar aqui e ir ao Google Play.

quinta-feira, 25 de julho de 2013

Plugin de Fonética Portuguesa para ElasticSearch

O ElasticSearch possui um plugin fonético que abrange diversas regras e padrões. No entanto nenhuma dessas regras é boa o bastante para as peculiaridades da lingua portuguesa.

Pensando nisso e de posse de uma gramática portuguesa, forkei o repositório e criei um encoder com as regras de nosso estimado idioma lusitano. Como alguns meses depois ainda não obtive resposta alguma da equipe do plugin original, promovi então este encoder a um novo plugin: Portuguese Phonetic Plugin.

Para utilizá-lo você precisa clonar o projeto no github e instalá-lo:

git clone https://github.com/timotta/elasticsearch-fonetica-portuguesa.git
cd elasticsearch-fonetica-portuguesa
./script/install.sh ~/Programas/elasticsearch-0.20.5
Depois é preciso configurar um analyser em config/elasticsearch.yml:
index :
    analysis :
        analyzer :
            fonetico :
                type : custom
                tokenizer : standard
                filter : [ standard, lowercase, foneticaportuguesa_filter, asciifolding ]
        filter :
            foneticaportuguesa_filter:
                type : foneticaportuguesa
                replace : false
Com tudo configurado, você pode criar um novo indice usando este analyser, como é mostrado abaixo:
$ curl localhost:9200/comfonetica -XPUT -d '
{
  "mappings" :{
     "document" : {
        "analyzer" : "fonetico"
     }
   }    
}'
Com o indice criado e configurado é possivel verificar as transformações de texto da seguinte forma:
$ curl -XGET 'localhost:9200/comfonetica/_analyze?analyzer=fonetico&pretty=true' -d chiclete
{
  "tokens" : [ {
    "token" : "XICLETE",
    "start_offset" : 0,
    "end_offset" : 8,
    "type" : "",
    "position" : 1
  }, {
    "token" : "chiclete",
    "start_offset" : 0,
    "end_offset" : 8,
    "type" : "",
    "position" : 1
  } ]
Repare que a palavra se transformou em duas. Isso acontece porque a configuração replace do filter está como false.

Atualmente só testei o plugin com a versão 0.20.5 do elasticsearch, se testarem em outras versões peço que reporte no github. Além disso, nem todas as regras fonéticas foram implementadas, então se você precisar de alguma que esteja faltando, colabore ou solicite lá também.

terça-feira, 16 de julho de 2013

Complexidade Ciclomática com Pygenie recursivo

Pygenie é uma biblioteca python bem simples para calcular a complexidade ciclomática de seus método. É tão simples, mas tão simples que para projetos mais complexos é preciso scriptar um pouco para que todos os diretórios sejam analisados.

Para facilitar então a nossa vida aqui na empresa, fiz uma contribuição (com pouca esperança de ser aceita pois o projeto está parado a dois anos) para que seja possível obter os resultado de forma recursiva. Com esta contribuição é possivel informar o parâmetro -r que analisará recursivamente todos os arquivos .py dentro de um diretório.

$ pygenie complexity mylib -r

Se dentro de seu projeto houver algum arquivo ou diretório que você não deseja que seja analisado você pode utilizar um outro parâmetro adicionado, o -e, onde você informar um pattern para ser excluído. Por exemplo se houver um diretório de testes:

$ pygenie complexity mylib -r -e tests

Caso você deseje utilizar desde já pode instalar o pygenie através do meu fork e neste meio tempo tentar incentivar nosso amigo Matthew Von Rocketstein a aceitar o pull request.

SlimIt, Head.js e django compressor

Ao configurar o django compressor com o filtro do SlimIt, o arquivo do head.js comprimido pela templatetag compress era gerado com um caracter  ï ao ínicio. Utilizando o SlimIt na mão eu obtinha o seguinte erro:

$ slimit head.js
Illegal character '\xbb' at 1:1 after LexToken(ID,'\xef',1,0)
Illegal character '\xbf' at 1:2 after LexToken(ID,'\xef',1,0)

Criei uma issue no repositório do SlimIt acreditando ser um problema desta lib. E determinado a corrigir tal problema acabei descobrindo que a raíz deste era o Head.js. No arquivo fonte desta biblioteca javascript havia realmente um caracter estranho, que nenhum editor exibia, nem mesmo o Vi. No entanto, se abríssemos o arquivo via python com um simples:

open("head.js").read()

Víamos claramente o caracter escondido. Espantosamente o único editor que testei e que exibiu o caracter estranho foi o editor online do github.

Forked, pull resqueted e merged: Felizes para sempre.

quarta-feira, 21 de novembro de 2012

Liberando o GIL do python para paralelizar seu código com threads

No Python o Global Interpreter Locker impede que duas threads executem ao mesmo tempo. Uma thread só é executada quando nenhuma outra estiver executando. A solução mais comum para aproveitar todos os cores de uma máquina em python é abandonar threads e utilizar vários processos. No entanto há uma maneira de aproveitar todos os cores com threads no python. Para isso vamos precisar criar uma extensão em C.

Primeiro vamos criar uma extensão que não libere o GIL para podermos comparar e conferir a melhoria de performance depois. O pivô dessa extensão é a seguinte função:

static int reduce_com_gil(int max, int (*f)(int x, int y)) {
   int retorno = 0;
   int i;
   for(i=0; i < max; i++){
       retorno = (*f)(retorno, i);
   }
   return retorno;
}

Essa função faz algo similar ao que um reduce faria, mas indo de 0 ao valor indicado em max. Para cada iteração a função enviada como segundo parâmetro é executada. A idéia é causar um grande processamento para que meu core fique travado. O uso dela está descrito no código abaixo:

static PyObject *antigil_calcular_com_gil(PyObject *self) {
   int valor = reduce_com_gil(100*1000, *antigil_calculos);
   char numero [5000];
   sprintf(numero, "%d", valor );
   return Py_BuildValue("s", numero);
}


Repare que eu passo para a função reduce_com_gil o ponteiro da função antigil_calculos. Essa outra função faz diversos cálculos a cada iteração do reduce. O nome antigil é o nome da extensão de exemplo. O código completo da extensão pode ser visto aqui.

Instalada a extensão, podemos testar a performance da lib com o seguinte código:

import antigil
antigil.calcular_com_gil()


E o seguinte comando:

$ time python teste.py
real 0m2.702s
user 0m2.692s


Mas o que a gente quer é saber como se comportam as threads. No caso o seguinte script abre 4 threads para aproveitar os 4 cores da minha máquina:

import antigil
from threading import Thread

threads = []
for i in xrange(4):
   t = Thread(target=antigil.calcular_com_gil)
   t.start()
   threads.append(t)

for t in threads:
   t.join()


No entando, acaba não aproveitando. Repare que ao executar 4 threads, o GIL age e impede que duas sejam executadas ao mesmo tempo. Dessa forma só um core da máquina é aproveitado. Isso pode ser observado pelo tempo total que é aproximadamente quatro vezes o tempo de execução de uma:

$ time python teste.py
real 0m10.910s
user 0m10.881s


Bom, vamos então à extensão não bloqueante. A chave do sucesso nesse caso são as macros Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS que respectivamente liberam o GIL e obtem o GIL de volta. Essas macros estão definidas em Python.h. O código da função reduce ficaria assim:

static int reduce_sem_gil(int max, int (*f)(int x, int y)) {
   int retorno = 0;
   Py_BEGIN_ALLOW_THREADS
   int i;
   for(i=0; i < max; i++){

       retorno = (*f)(retorno, i);
   }
   Py_END_ALLOW_THREADS
   return retorno;
}


Pronto, basta criar a função antigil_calcular_sem_gil similar à antigil_calcular_com_gil, utilizando a função reduce_sem_gil e então podemos repetir o teste. No caso parametrizei o script de teste para que você possa escolher qual função deseja executar:

import antigil
from threading import Thread
import sys

if 'com-gil' in sys.argv:
    calcular = antigil.calcular_com_gil
elif 'sem-gil' in sys.argv:
    calcular = antigil.calcular_sem_gil
else:
    print "Informar com-gil ou sem-gil"
    exit(0)

threads = []
for i in xrange(4):
   t = Thread(target=calcular)
   t.start()
   threads.append(t)

for t in threads:
   t.join()


O resultado é bem animador e mostra bem que o código rodou em paralelo:

$ time python teste.py sem-gil
real 0m3.414s
user 0m13.413s


Como o código utiliza apenas CPU, sem IO algum, ao aumentar para 8 threads, mesmo a versão que libera o GIL dobra de tempo pois só tenho disponível 4 cores na minha máquina. No entanto acredito que se fizer alguma operação de IO entre as macros Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS outras threads poderão ser executadas no caminho. Isso eu ainda preciso validar.

Só é preciso ter muito cuidado pois código entre essas macros está em território perigoso. A alteração de váriaveis globais ou ponteiros que podem ser compartilhados entre outras threads podem causar erros inesperados a qualquer momento. Portanto, é importante utilizar somente váriaveis locais e dados copiados.

O código completo da extensão em C antigil pode ser visto aqui.


quinta-feira, 9 de agosto de 2012

Ganhe convites de graça para a SEMCOMP


Este ano o maior evento de tecnologia da Bahia, a Semana da Computação da UFBA, será fantástica. A lista de palestrantes já confirmados é um dos pontos que chama a atenção, com presença de Nívio Ziviani, Osvaldo Matos, Fábio Akita, Sérgio Cavalcante entre outros. Você pode ver a lista completa dos palestrantes aqui: http://infojr.com.br/semcomp/palestrantes.

Os dois primeiros lotes de entradas ao evento se esgotaram logo nos primeiros dias de lançamento, e o terceiro pode estar em vias de acabar. Como a Globo.com está patrocinando o evento, ela me deu cinco convites para que eu distribuisse por conta própria. Resolvi então fazer de uma maneira divertida.

Para garantir o seu convite grátis para a SEMCOMP, basta fazer três coisas:

  1. Curtir o Música.com.br (novo site de música da Globo.com) no Facebook.
  2. Criar uma playlist no Música.com.br.
  3. Enviar a url dela para timotta@gmail.com indicando em quais quesitos ela se encaixa, lembrando de colocar no assunto do e-mail: Playlist SEMCOMP.

O criador da melhor playlist em cada um dos seguintes quesitos abaixo leva um convite para o evento.

  • Melhor playlist para programar em par
  • Melhor playlist para resolver bugs
  • Melhor playlist para configurar servidor
  • Melhor playlist para ajeitar layout web
  • Melhor playlist para programar sozinho

Você pode criar quantas playlists quiser. O próprio time de desenvolvimento do Música.com.br escolherá, em um método científico de experimentação, ou seja programando usando as playlists enviadas. Os vencedores serão anunciados apartir de 10 de Setembro de 2012 na página do facebook da SEMCOMP.

Mas veja que são apenas os convites. Não está incluído aí passagem ou hospedagem. Ou seja, caso você more em outro estado ou cidade, você irá arcar com esses custos. Será oferecido apenas o convite.

Para servir de inspiração, segue algumas das playlists que eu criei:


Que a diversão comece!

domingo, 22 de julho de 2012

Divagações sobre GIL, threads e IO em python e ruby


Uma coisa que eu sempre me confundi sobre ruby e python é a questão do Global Interpreter Locker (GIL). Na verdade, a dúvida maior é se operações de IO realmente bloqueiam os processos, impedindo a execução de outras threads.

Recentemente li um pouco mais sobre a versão 1.9 do Ruby e como ela passou a utilizar threads do sistema operacional, ao contrário das chamadas Green Threads da versão 1.8. No entanto, o GIL do ruby continua impedindo que duas threads executem ao mesmo tempo. A menos que uma esteja parada executando alguma operação de IO não bloqueante.

No caso do Python, o pouco de informação que tenho me leva a crer que a linguagem utiliza Green Threads. Fica então a minha dúvida se mesmo assim é possivel que o processo execute uma outra thread enquanto aguarda um retorno de IO.

Para começar fiz um pequeno script python para simular uma query pesada do MySql sendo executada 4 vezes. Se o script demorar por volta de dois segundo significa que durante uma operação de IO, o python prosseguiu executando as outras threads:

import _mysql
from threading import Thread

def executa():
    db = _mysql.connect(host="localhost",

                        user="root",
                        passwd="",

                        db="teste")
    db.query("select sleep(2)")
    r=db.use_result()
    r.fetch_row()

threads = []
for i in range(4):          
    t = Thread(target=executa, args=())
    t.start()
    threads.append(t)

for t in threads:
    t.join()
 
O resultado da execução pode ser visto abaixo, mostrando que o IO não bloqueou o programa:

> time python teste.py
real 0m2.030s
user 0m0.016s
sys 0m0.012s

Executei o mesmo teste com ruby, com o script similar abaixo:

require 'mysql2'

threads = []
4.times do |i|
    thread = Thread.new do
        my = Mysql2::Client.new(host: "127.0.0.1", 

                                username: "root", 
                                database: "teste")
        my.query("select sleep(2)").collect{|i|i}
    end
    thread.run
    threads << thread
end

threads.each do |thread|  
    thread.join
end

E o resultado também foi satisfatório:

> time ruby teste.rb
real 0m2.178s
user 0m0.076s
sys 0m0.008s

Ou seja, tanto python como ruby estão lidando bem com execução paralela. Mesmo se não utilizar todos os cores disponiveis, no mínimo o IO para o MySql não está bloqueando.

Com esse bom resultado, resolvi então subir um pouco mais de nível e verificar se colocando o Django na equação poderíamos aproveitar esse bom desempenho. Criei essa pequena view para simular uma query lenta, assim como nos scripts acima, e iniciei o Django (versão 1.3) com o gunicorn.

from django.db import connection
def debug(request):
    cursor = connection.cursor()
    cursor.execute("select sleep(2)")
    return HttpResponse('ok')

E o resultado, como pode ser visto abaixo, foi ruim:

> ab -n 4 -c 4 http://127.0.0.1:8000/debug/
Time taken for tests:   8.161 seconds

O mesmo teste executado para a dupla ruby on rails tem resultado parecido:

> ab -n 4 -c 4 http://localhost:3000/politicos/
Time taken for tests:   8.043 seconds

Conclusão:

Embora python e ruby permitam IO não bloqueante, os frameworks Django e Rails ainda são bloqueantes. Um dos motivos daqueles memes de "Rails não escala" e "Django não escala". Felizmente sempre há alternativas.

É possivel escalar via processos como explicado em Solucionando IO bloqueante do mysql para Rails, e utilizando vários workers do gunicorn para Django. Caso seja necessário uma escalabilidade maior ainda, o ideal então é partir para soluções como Event Machine do ruby, GEvent para Python, ou até mesmo NodeJs. Combinando essas soluções com múltiplos processos.