Credo! Um linter para elixir?

Continuando no tema de Elixir, hoje René Föhring lançou o Credo, um linter “que ensina”. Linters são programas que leem o código fonte e fazem uma análise mostrando potenciais problemas ou pontos de melhoria nesse código, desde formatação até coisas mais escopo de variáveis, etc.

No post de anúncio, René discorre sobre como as ferramentas do Elixir costumam dar as mensagens de erro de maneira informativa, apontando uma possível solução ao invés de apenas culpar o desenvolvedor. Ok, vez por outra ainda aparecem algumas mensagens criptografadas mas a tendência é melhorar cada vez mais.

Seguindo essa linha, o Credo busca dar mensagens bem didáticas, ainda com a opção de mostrar uma explicação mais detalhada sobre o item. Abaixo, segue um exemplo do que ele reportou num pet project que comecei hoje (por isso tão poucos erros 😀 ):

  Warnings - please take a look                                                                                                                                                                 
┃ 
┃ [W] ↗ Parameter `k` has same name as a function in the same module.
┃       lib/raycifex/vector.ex:34:33 (Raycifex.Vector.scalar_mult)
┃ [W] ↗ There should be no calls to IO.inspect/1.
┃       lib/raycifex.ex:5:5 (Raycifex.main)
┃ [W] ↗ There should be no calls to IO.inspect/1.
┃       lib/raycifex.ex:6:5 (Raycifex.main)

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.1 seconds (0.06s to load, 0.04s running checks)
17 mods/funs, found 3 warnings.

Only considering priority objects: ↑ ↗ →  (use `--help` for options).

Dessas, apenas o ‘k’ repetido que assumo que realmente foi um desleixo, já que os inspect foram conscientes. Dado um aviso desses, você pode pegar o id do erro – isto é, o arquivo + linha/coluna – e rodar o credo novamente, para ver a explicação detalhada.

Para ver um exemplo mais abrangente e mais informações sobre o projeto, podem ver o post de anúncio, linkado acima.

o/

PS: Parabéns para René, tanto pela ferramenta como pelo nome escolhido, que permitiu a “piada” do título…

Raycife: Agora com sombras

Apesar de ter aumentado consideravelmente a granularidade (tá parecendo coisa de z-buffer ou algo do tipo), corrigi um bug besta pelo qual as sombras não estavam aparecendo: O raio de luz estava com a direção invertida (luz – ponto_de_verificação, e não ponto_de_verificação – luz)…

Abaixo, o resultado: 100×100 pixels e 100 raios/pixel (26 minutos, mas usando o computador enquanto processava):

Próximos passos: resolver essa bronca de granularidade e adicionar reflexão especular.

Status do projeto: raycife+

First full window of raycife

Esse é o estado atual, apenas com iluminação ambiente e a componente difusa (e um belo bug de ordenação). A imagem de referência pode ser encontrada no fim dessa página.

Dados: Janela 200×200 com 20 raios por pixel.

Tempo de processamento num Celeron 440 com 1 GB de RAM: +-15 minutos (muito lento…)

Profiling python com cprofile

Durante o desenvolvimento do raycife+, a performance estava ficando impraticável, chegando a cerca de 3 a 4 minutos para uma cena simples de 100×100 pixels e 7 objetos no meu velho celeron. Seguindo as dicas desse site, rodei o cprofile e pude ver onde poderia ganhar tempo.

No arquivo principal do programa esse código que executa a função main com os argumentos fornecidos na string e salva o resultado num arquivo de log.

if __name__ == '__main__':
    import cProfile
    cProfile.run('main(["raycifeplus.py", "./samples/cornellroomsmall.sdl"])' filename='raycife.cprof')

Para ler o resultado do arquivo de saída:

from pstats import Stats
stats = Stats("raycife.cprof")
stats.sort_stats('time').print_stats()

Exemplo da saída:

Mon Apr 21 11:23:35 2008    raycife.cprof

37298787 function calls in 237.746 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1  168.955  168.955  237.734  237.734 ./raycifeplus.py:71(render)
26660558   46.400    0.000   46.409    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:165(__iter__)
5723042    9.791    0.000    9.807    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:265(__sub__)
2422914    4.633    0.000    4.636    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:369(__rmul__)
2422914    4.114    0.000    4.125    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:224(__add__)
10000    2.927    0.000    4.841    0.000 ./raycifeplus.py:80(render_pixel)
1472    0.478    0.000    0.916    0.001 ./raycifeplus.py:99(trace_point)
841    0.129    0.000    0.299    0.000 /home/lauro/dev/raytracer/raycifeplus/objfile.py:30(intersect)
1    0.119    0.119    0.182    0.182 /home/lauro/dev/raytracer/raycifeplus/pnm.py:18(write)
10000    0.044    0.000    0.044    0.000 /home/lauro/dev/raytracer/raycifeplus/pnm.py:15(set_pixel)

Nesse caso, deu para ver que parte do tempo era perdido no módulo vector3, uma implementação de vetores em 3 dimensões usando python.

No próximo vou falar sobre uma tentativa rápida de implementar a mesma funcionalidade usando C++ e criando o binding usando o SIP.

Nota: O código acima ainda poderia ser refinado para receber argumentos na linha de comando mas como é apenas um teste rápido, pode ficar p/ depois uma flag “-p” para habilitar o profiling.

Nota2: Sim, eu sei que a otimização prematura é a raíz de todo mal mas nesse caso é um teste pontual que não vai mudar o algoritmo, além de poder conhecer novas tecnologias 🙂