Back-End

10 mai, 2013

Cálculo WGS-84 na velocidade do C

Publicidade

Quando começamos a administrar a frota da Visual Units, uma coisa que foi muito difícil de conseguir fazer direito foi o cálculo das distâncias. Não havia um fim das informações disponíveis e quase tudo estava em um nível de matemática muito além de um pobre desenvolvedor que considera que qualquer coisa além de matemática discreta (ou finita), geometria básica e estatística, já é problema de outra pessoa. As implementações que puderam ser encontradas eram versões proprietárias com que não podíamos arcar naquele estágio.

Por um tempo, resolvemos o problema utilizando uma solução que dependia da projeção cônica de Lambert – era precisa o suficiente, mas não perfeita, e nossos mapas utilizavam a mesma projeção, portanto funcionou – apesar do fardo de ter que transformar nossas coordenadas WGS-84 em Lambert toda vez que o cálculo precisasse ser feito. Alguns anos atrás, entretanto, mudamos para a API do Google Mapas e por isso o Lambert não tinha mais serventia alguma – e o aumento da demanda por carga e precisão fez com que utilizar a solução atual fosse uma escolha cada vez pior.

Aqui entra Chris Veness. Ou melhor, sua implementação da fórmula de inversão de Vincenty (pdf). Apesar de a matemática estar além da minha compreensão, portar a implementação JavaScript para Python foi algo objetivo e direto e alguns testes mostraram que o resultado era mais rápido e preciso do que a solução anterior.

Adiantando alguns meses, de repente o desempenho começa a parecer algo que podia se tornar um problema. Temos muitos motivos para calcular distâncias, e enquanto os trabalhos em lote não fossem um problema, qualquer quantidade de tempo que pudesse ser economizada de ações iniciadas por usuários é muito bem-vinda.

Então, pensei comigo mesmo: “eu portei uma vez, seria difícil fazer isso de novo”? Afinal, quando velocidade torna-se um problema, o programador Python acaba deparando-se com o C. Portar o código foi novamente algo bem objetivo, ao mapear as funções do Python

    def distance(x1, y1, x2, y2):
        ...

em

const double distance(const double x1, const double y1,
                      const double x2, const double y2)

O código C resultante é quase idêntico às implementações em Python (e JavaScript), mas executa 6 vezes mais rápido do que a implementação em Python. Permitir que submissões de cálculos em lote, em vez de chamadas para cada cálculo, e eliminar algum overhead FFI, iriam aumentar a velocidade um pouco.

$ python2.7 -m tests.test_distance
Time elapsed for 100000 calculations in
    Python: 1952.70
    C: 300.46
    Factor: 6.50

Embrulhar o código C e chamá-lo foi simples utilizando ctypes, e adicionei um fallback para a implementação em Python caso a biblioteca compartilhada em C não puder ser encontrada; um pequeno __init__.py no pacote conecta a versão correta:

from .distance import distance as _py_distance
try:
    from ctypes import cdll, c_double
    dll = cdll.LoadLibrary('cDistance.so')
    dll.distance.restype = c_double
    dll.distance.argtypes = [c_double, c_double, c_double, c_double]
    distance = dll.distance
    _c_distance = dll.distance
except OSError: #Fall back to Python implementation
    distance = _py_distance

É claro que isso depende do código C ser compilado com cDistance.so e que o arquivo esteja disponível para um link – e que ele mantenha o .so inseridos diretamente no programa (hardcoded) para que uma DLL do windows não realize o trabalho. Eu pretendia trabalhar na limpeza do código um pouco mais antes de torná-lo open source, mas já que venho querendo abrir algumas de nossas ferramentas há anos e nunca encontro tempo, pensei que era melhor jogá-lo lá, e adiar a intenção de deixá-lo mais bonito. Espero que alguém ache algum uso para ele. Eu vou tentar fazer uma limpeza e empacotá-lo assim que possível.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://blaag.haard.se/WGS-84-distance-calculations-at-the-speed-of-C/#disqus_thread