Profile de List Comprehensions Em Python

Em desenvolvimento de software sempre é comum ouvirmos aquela pergunta “E esse código é performático?”. Apesar de nem sempre ser o mais importante a se saber, essa pergunta é bem frequente e eu mesmo me peguei com essa indagação com as list comprehensions do Python. Então, resolvi fazer um profile para tirar as minhas dúvidas.

As dúvidas surgiram durante o curso Python Para Quem Sabe Python do Luciano Ramalho. O curso é excelente e estou vendo vários conceitos mais internos do Python em que eu nunca tive a oportunidade de me aprofundar. Durante a primeira aula, foi mostrado a list comprehension e a comparação com um laço for normal focando na legibilidade do código. Depois disso, perguntei sobre a performance e o pessoal deu a ideia implementarmos um profile para medirmos essa questão e experimentarmos nós mesmos esse processo.

O João Dubas, que também está no curso, começou com uma abordagem em que me baseei e fiz um fork. Na minha medição, quis comparar alguns cenários, sendo eles:

  1. Somente o filter de uma lista;
  2. Somente o map para uma lista;
  3. Filter e map para uma lista;

Para cada um desses cenários existiam 3 implementações – uma com o laço for normal, uma com list comprehension e outra utilizando as funções map e filter do Python. A ideia desse post é compartilhar, sem nenhuma pretensão científica, os resultados que obtive. Então, segue o output:

***** FILTER PROFILING *****
                              fn min        mean       max      
        filter_with_normal_for() 0.35435510 0.35819454 0.36282516
filter_with_list_comprehension() 0.30340981 0.31135285 0.32254100
       filter_with_filter_func() 0.21948600 0.22835863 0.26977205

***** MAP PROFILING *****
                           fn min        mean       max      
        map_with_normal_for() 0.36617208 0.37257178 0.39629292
map_with_list_comprehension() 0.29344010 0.29665232 0.30447888
          map_with_map_func() 0.28689599 0.29024918 0.30203104

***** MAP + FILTER PROFILING *****
                                 fn min        mean       max      
        mapfilter_with_normal_for() 0.46366715 0.47172487 0.48409891
mapfilter_with_list_comprehension() 0.42643499 0.42990112 0.43452096
    mapfilter_with_mapfilter_func() 0.36422396 0.36758418 0.38192511

Se os cenários que eu escrevi estiverem corretos e não ter comido nenhuma mosca com algum detalhe, em todos os cenários as list comprehension se mostraram a segunda melhor solução, perdendo para a utilização das funções bultins do Python. No segundo cenário, ela ficou bem próxima da implementação utilizando o map. Isso dá para supor que a diferença de performance ficou a cargo do filter e não no map. Em todos os casos a solução normal utilizando o laço for é a menos performática.

Como disse, essa medição não teve nenhum pretexto científico e foi apenas uma maneira divertida de fomentar uma discussão muito interessante na lista da turma do PPQSP sobre o valor da performance do código em detrimento da sua legibilidade. Eu também sou da escola do Luciano Ramalho ao gostar das list comprehensions por elas deixarem claras quais as intenções do código. Mas também compreendo que podem existir casos em que esse tipo de preocupação com performance existem, apesar de não achá-los comuns. Portanto, primeiro escrevo o código bonito para depois otimizá-lo se necessário!

O código que eu fiz para fazer essa medição está nesse repositório do Github e você pode executar o script na sua máquina para ver quais os seus resultados executando os comandos abaixo:

$ wget https://raw.github.com/berinhard/ppqsp/master/class_0/for_profiler.py
$ python for_profiler.py

Espero que vocês se divirtam fazendo as medições e sintam-se a vontade para compartilhar os resultados aqui nos comentários!

Share on Facebook
Post to Google Buzz

Leave a comment

Your comment