Posts de November, 2012

[Andrews Medina] minhas apresentações na pythonbrasil[8]

Thursday, November 29th, 2012

Aconteceu entre os dias 21 a 14 de novembro a oitava edição da pythonbrasil.

O evento foi ótimo, com vários pythonistas do mundo todo. Vários treinamentos, palestras e palestras informais (conversas no bar e no corredor) de alto nível.

Eu participei ministrando um treinamento e apresentei 3 palestras. O treinamento foi sobre Python 3, e as palestras foram sobre Python 3, Design de Código e Escalabilidade de Aplicações Web. A palestra sobre escalabilidade eu apresentei em conjunto com a minha amiga Flávia Missi.

Para quem quiser, os slides das palestras no slideshare:

Python 3 - tutorial from Andrews Medina
Python 3 from Andrews Medina
Escalando aplicações web from Andrews Medina
Python 3 from Andrews Medina

Não vou escrever muito sobre como foi o evento, mas gostaria de declarar que foi a melhor pythonbrasil que eu já fui!

[Tiago Motta] Liberando o GIL do python para paralelizar seu código com threads

Wednesday, November 21st, 2012

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
       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       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.

[Igor Sobreira] Python coverage threshold

Tuesday, November 6th, 2012

Published on: 05/11/2012 09:48h

Ned Batchelder committed a new feature to coverage.py 2 days ago, it’s a way to verify coverage threshold. Basically a way to fail your tests if the coverage is not enough.
For now you need to install from the repository to get the new feature

$ pip install hg+https://bitbucket.org/ned/coveragepy#egg=coverage

This is how to use from the command line:

$ coverage run run_my_tests.py ... running all tests$ coverage report --fail-under=100... display the report$ echo $?2

If your coverage is not 100% your exit status will be 2. This will make your CI fail if your coverage is not enough :)
You may also verify coverage using the python API, in this case you need to verify the return value from report() function, here is an example:

cov = coverage.coverage(..)cov.start()ret = run_all_my_tests()cov.stop()if ret == 0:    covered = cov.report()    assert covered > 100, "Not enough coverage"...

I’ve created a decorator to make this easier:

def ensure_coverage(percentage, **cov_options):    def decorator(function):        @wraps(function)        def wrapper(*args, **kw):            cov = coverage.coverage(branch=True, **cov_options)            cov.start()            ret = function(*args, **kw)            cov.stop()            if ret == 0:                covered = cov.report()                assert covered >= percentage, \                    "Not enough coverage: {0:.2f}%. You need at least {1}%".format(covered, percentage)            return ret        return wrapper    return decorator

This is an usage example for this django app I’m working on:

@ensure_coverage(99, source=['filecabinet'], omit=['filecabinet/tests/*'])def runtests():    test_runner = get_runner(settings)()    return test_runner.run_tests(['filecabinet'])

if __name__ == '__main__':    sys.exit(runtests())

Here are the related commits, if you’re interested:

https://bitbucket.org/ned/coveragepy/changeset/7ea709fc4c1190cf0ffe0aba1a49e6fffe683d2f
https://bitbucket.org/ned/coveragepy/changeset/90014f4defd336f05851bdfc01c2b5af60a933c9
https://bitbucket.org/ned/coveragepy/changeset/ba7267fe525001dba99ed1c2c9d11f0724ad9950

And the discussion on the issue:

https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests

By Igor Sobreira

[Igor Sobreira] Python coverage threshold

Monday, November 5th, 2012

Ned Batchelder committed a new feature to coverage.py 2 days ago, it’s a way to verify coverage threshold. Basically a way to fail your tests if the coverage is not enough.
For now you need to install from the repository to get the new feature

$ pip install hg+https://bitbucket.org/ned/coveragepy#egg=coverage

This is how to use from the command line:

$ coverage run run_my_tests.py
... running all tests
$ coverage report --fail-under=100
... display the report
$ echo $?
2

If your coverage is not 100% your exit status will be 2. This will make your CI fail if your coverage is not enough :).
You can also verify coverage using the python API, in this case you need to verify the return value from report() function, here is an example:

cov = coverage.coverage(..)
cov.start()
ret = run_all_my_tests()
cov.stop()
if ret == 0:
    covered = cov.report()
    assert covered > 100, "Not enough coverage"
...

I’ve created a decorator to make this easier:

def ensure_coverage(percentage, **cov_options):
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kw):
            cov = coverage.coverage(branch=True, **cov_options)
            cov.start()
            ret = function(*args, **kw)
            cov.stop()
            if ret == 0:
                covered = cov.report()
                assert covered >= percentage, \
                    "Not enough coverage: {0:.2f}%. You need at least {1}%".format(covered, percentage)
            return ret
        return wrapper
    return decorator

This is an usage example for this django app I’m working on:

@ensure_coverage(99, source=['filecabinet'], omit=['filecabinet/tests/*'])
def runtests():
    test_runner = get_runner(settings)()
    return test_runner.run_tests(['filecabinet'])

if __name__ == '__main__':
    sys.exit(runtests())

Here are the related commits, if you’re interested:

https://bitbucket.org/ned/coveragepy/changeset/7ea709fc4c1190cf0ffe0aba1a49e6fffe683d2f
https://bitbucket.org/ned/coveragepy/changeset/90014f4defd336f05851bdfc01c2b5af60a933c9
https://bitbucket.org/ned/coveragepy/changeset/ba7267fe525001dba99ed1c2c9d11f0724ad9950

And the discussion on the issue:

https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests