Upload
vitor-capela
View
232
Download
4
Embed Size (px)
DESCRIPTION
Às vezes o Ruby não resolve nosso problema, mesmo com o melhor dos algoritmos. Não se acanhe: o C é seu amigo, e nada mais fácil que aproveitar uma biblioteca já existente com Ruby FFI.
Citation preview
FFI História, performance e felicidade com Ruby
Vitor Capela
domingo, 3 de março de 13
@dodecaphonic
domingo, 3 de março de 13
Sou @dodecaphonic no Twitter. Fico com essa cara quando o computador não faz o que eu mando.
Meu trabalho às vezes exige que eu transforme isto...
domingo, 3 de março de 13
Eu trabalho com geoprocessamento. Isso envolve tanto fazer coisas babacas com Google Maps como transformar uma nuvem de pontos de um levantamento a laser...
... nisto.
domingo, 3 de março de 13
... em uma malha de triângulos. Essa malha pode ser um terreno em que algo vai ser construído, pode ser o levantamento do relevo para um estudo hidrológico, pode ser um scan para modelagem de um projeto de reconstrução.
A gente ama Ruby.O Ruby ama a gente também.❤
domingo, 3 de março de 13
E eu gosto de fazer isso com Ruby. É muito gratificante dar uma solução sucinta, ou então bem flexível, a problemas que em linguagens tradicionais do mercado seriam enfadonhos. Há oito anos ele é meu canivete suíço.
A gente ama Ruby.O Ruby ama a gente também.❤
domingo, 3 de março de 13
E eu gosto de fazer isso com Ruby. É muito gratificante dar uma solução sucinta, ou então bem flexível, a problemas que em linguagens tradicionais do mercado seriam enfadonhos. Há oito anos ele é meu canivete suíço.
Mas às vezes o amor não resolve.
domingo, 3 de março de 13
Sendo muito honesto, no entanto, nem sempre é a solução ideal.
Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante.
domingo, 3 de março de 13
Às vezes seus problemas recaem justamente sobre os pontos fracos da linguagem: você precisa de previsibilidade nas alocações de memória, de altíssimo desempenho numérico, de soluções que tomem o mínimo possível de tempo.
Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante.
“Não é verdade: você precisa apenas de algoritmos melhores!”
domingo, 3 de março de 13
Às vezes seus problemas recaem justamente sobre os pontos fracos da linguagem: você precisa de previsibilidade nas alocações de memória, de altíssimo desempenho numérico, de soluções que tomem o mínimo possível de tempo.
Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante.
“Não é verdade: você precisa apenas de algoritmos melhores!”
“Quem se importa? 99% dos problemas se resumem a IO.”
domingo, 3 de março de 13
Às vezes seus problemas recaem justamente sobre os pontos fracos da linguagem: você precisa de previsibilidade nas alocações de memória, de altíssimo desempenho numérico, de soluções que tomem o mínimo possível de tempo.
Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante.
“Não é verdade: você precisa apenas de algoritmos melhores!”
“Quem se importa? 99% dos problemas se resumem a IO.”
E se eu já tiver tentado ?
domingo, 3 de março de 13
Às vezes seus problemas recaem justamente sobre os pontos fracos da linguagem: você precisa de previsibilidade nas alocações de memória, de altíssimo desempenho numérico, de soluções que tomem o mínimo possível de tempo.
Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante.
“Não é verdade: você precisa apenas de algoritmos melhores!”
“Quem se importa? 99% dos problemas se resumem a IO.”
E se eu já tiver tentado ?
Pois é: 99%, não 100%.
domingo, 3 de março de 13
Às vezes seus problemas recaem justamente sobre os pontos fracos da linguagem: você precisa de previsibilidade nas alocações de memória, de altíssimo desempenho numérico, de soluções que tomem o mínimo possível de tempo.
“Então vá programar em <Python|Java|Scala|Clojure|Haskell...>!”
domingo, 3 de março de 13
Já ouvi e já disse para mim mesmo algumas vezes: vá para outras linguagens. E já fui: resolvo e resolvi coisas com C++, aplico Scala aqui e ali, faço o que for preciso se o Ruby não der conta.
A gente ama o Ruby.O Ruby ama a gente também.❤EU
MIMAMO
domingo, 3 de março de 13
Mas isso não muda o fato de que quero usá-lo sempre que possível.
A gente ama o Ruby.O Ruby ama a gente também.❤EU
MIMAMO
domingo, 3 de março de 13
Mas isso não muda o fato de que quero usá-lo sempre que possível.
Se eu quero me manter programando em Ruby sem comprometer as necessidades dos meus projetos, a solução é apelar para o C*.
* Depois de tentar o JRuby, claro.
domingo, 3 de março de 13
Com isso em mente, sempre que um problema aperta eu tento primeiro ir para o JRuby (com invokedynamic ligado); se ainda assim não for o bastante, meu (nosso) melhor amigo ainda é o C.
RUBY & Cdomingo, 3 de março de 13
A gente ainda estava aprendendo o beabá e o C já resolvia problemas muito complicados.
domingo, 3 de março de 13
Às vezes a gente tem a impressão de que o mundo começou quando passamos a dar atenção a ele. Que todos os nossos problemas são novos ou únicos de alguma maneira. Os barbudos nos laboratórios escuros espalhados pelo mundo inventaram a Internet e o Unix enquanto a gente nem pensava em nascer.
Isso significa que há bibliotecas às pencas, várias extremamente maduras e mantidas há décadas.
domingo, 3 de março de 13
O legado (o BOM legado) é imenso.
Ainda não há uma gem para resolver qualquer parada.
domingo, 3 de março de 13
Por mais que milhares de gems pipoquem a cada mês, não há solução para tudo. Encontro isso todos os dias no meu trabalho.
ruby-ffi é o menor caminho entre seu programa Ruby e alguma biblioteca supimpa (e rápida!) que já esteja por aí.
domingo, 3 de março de 13
E para aproveitar esse legado, a melhor coisa atualmente é usar o ruby-ffi.
FOREIGNFUNCTIONINTERFACE
Java.NETPythonMobile JS
JNIP/InvokectypesPhoneGap
domingo, 3 de março de 13
O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito de ligar duas linguagens díspares de modo que uma possa fazer uso da outra.
FOREIGNFUNCTIONINTERFACE
Java.NETPythonMobile JS
JNIP/InvokectypesPhoneGap
Ruby
domingo, 3 de março de 13
O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito de ligar duas linguagens díspares de modo que uma possa fazer uso da outra.
FOREIGNFUNCTIONINTERFACE
Java.NETPythonMobile JS
JNIP/InvokectypesPhoneGap
Ruby
domingo, 3 de março de 13
O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito de ligar duas linguagens díspares de modo que uma possa fazer uso da outra.
FOREIGNFUNCTIONINTERFACE
Java.NETPythonMobile JS
JNIP/InvokectypesPhoneGap
Ruby ruby-ffi
domingo, 3 de março de 13
O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito de ligar duas linguagens díspares de modo que uma possa fazer uso da outra.
Uma FFI permite que você chame funções em uma outra linguagem.
domingo, 3 de março de 13
“Mas qual é a diferença de escrever uma extensão em C?”
domingo, 3 de março de 13
domingo, 3 de março de 13
Não é preciso saber programar em C, ou TER que programar em C, para poder usar uma biblioteca. Isso significa também que você não precisa compilar nada (o que é especialmente bom no Windows).
Você só precisa escrever Ruby.
domingo, 3 de março de 13
Não é preciso saber programar em C, ou TER que programar em C, para poder usar uma biblioteca. Isso significa também que você não precisa compilar nada (o que é especialmente bom no Windows).
Você não precisa de headers ou da versão de desenvolvimento da biblioteca para distribuir sua gem.
domingo, 3 de março de 13
Você pode usar o resultado em qualquer Ruby (MRI, JRuby, Rubinius, Maglev, ...) sem nenhuma modificação ou restrição.
domingo, 3 de março de 13
Seu resultado não fica restrito ao MRI, nem sujeito às idiossincrasias das camadas de adaptação de extensões C que o JRuby e Rubinius oferecem.
Você não corre o risco da sua gem quebrar quando/se mudar a API de extensão do MRI.
domingo, 3 de março de 13
É muito fácil de usar.
domingo, 3 de março de 13
MESHERATOR
domingo, 3 de março de 13
Para ilustrar o ganho que uma biblioteca em C pode trazer a um projeto, escrevi um pequeno demo. “MESHERATOR” - MESH GENERATOR, um gerador de malhas trianguladas. Muito criativo.
Transformar isto...
domingo, 3 de março de 13
E se vocês se lembram do começo da apresentação, meu objetivo era sair disto...
... nisto.
domingo, 3 de março de 13
... para isto.
Este cara sabe como triangular nuvens de pontos muito rapidamente.
Sua biblioteca, por não ser em Ruby, não tem um nome criativo ou engraçadinho: é TRIANGLE, mesmo.
JONATHAN SHEWCHUCK
domingo, 3 de março de 13
DEMO
domingo, 3 de março de 13
void triangulate(char *, struct triangulateio *, struct triangulateio *, struct triangulateio *); void trifree(VOID *memptr);
A API é bem pequena.
Uma string com as opções. A estrutura de entrada e saída do algoritmo.
domingo, 3 de março de 13
Não há muito a encapsular para usar essa biblioteca. char* em C é uma string. triangulateio é uma estrutura de dados. * é um ponteiro. “triangulate” transforma a nuvem de pontos em triângulos; “trifree” permite que eu libere a memória alocada pelo algoritmo.
struct triangulateio { REAL *pointlist; /* In / out */ REAL *pointattributelist; /* In / out */ int *pointmarkerlist; /* In / out */ int numberofpoints; /* In / out */ int numberofpointattributes; /* In / out */ int *trianglelist; /* In / out */ REAL *triangleattributelist; /* In / out */ REAL *trianglearealist; /* In only */ int *neighborlist; /* Out only */ int numberoftriangles; /* In / out */ int numberofcorners; /* In / out */ int numberoftriangleattributes; /* In / out */ int *segmentlist; /* In / out */ int *segmentmarkerlist; /* In / out */ int numberofsegments; /* In / out */ REAL *holelist; /* In / pointer to array copied out */ int numberofholes; /* In / copied out */ REAL *regionlist; /* In / pointer to array copied out */ int numberofregions; /* In / copied out */ int *edgelist; /* Out only */ int *edgemarkerlist; /* Not used with Voronoi diagram; out only */ REAL *normlist; /* Used only with Voronoi diagram; out only */ int numberofedges; /* Out only */ };
triangle.c:19 - #define REAL double
domingo, 3 de março de 13
A estrutura é bem compreensível e descritiva (supondo que você conheça o domínio). Os tipos de dados também. A única coisa pouco familiar é “REAL” — que, olhando no código, é apenas um double.
Vamos começar definindo nosso encapsulamento com FFI.
domingo, 3 de março de 13
require 'ffi' module Mesherator module TriangleFFI extend FFI::Library ffi_lib 'libtriangle'
typedef :pointer, :triangulateio attach_function :triangulate, [:string, :triangulateio, :triangulateio, :triangulateio], :void attach_function :trifree, [:pointer], :void endend
domingo, 3 de março de 13
Aqui defino então as duas funções. “attach_function” procura uma função de mesmo nome na biblioteca definida em “ffi_lib”. Os valores no array são os tipos de dados que a funcão recebe; o último argumento é o retorno. Como ambas não retornam nada (operam diretamente em ponteiros passados para elas), declaro como :void.
Uma struct básica é mapeada com FFI::Struct.
domingo, 3 de março de 13
class TriangulateIO < ::FFI::Struct layout :pointlist, :pointer, :pointattributelist, :pointer, :pointmarkerlist, :pointer, :numberofpoints, :int, :numberofpointattributes, :int, :trianglelist, :pointer, :triangleattributelist, :pointer, :trianglearealist, :pointer, :neighborlist, :pointer, :numberoftriangles, :int, :numberofcorners, :int, :numberoftriangleattributes, :int, :segmentlist, :pointer, :segmentmarkerlist, :pointer, :numberofsegments, :int, :holelist, :pointer, :numberofholes, :int, :regionlist, :pointer, :numberofregions, :int, :edgelist, :pointer, :edgemarkerlist, :pointer, :normlist, :pointer, :numberofedges, :intend
Todo ponteiro (*) vira :pointer
domingo, 3 de março de 13
Então eis a estrutura análoga em Ruby. Mantive os mesmos nomes, exatamente, para facilitar o entendimento, mas não é obrigatório: o mais importante é manter a ordem dos tipos de dados.
layout garante que o bloco de memória criado em C se encaixará como uma luva.
domingo, 3 de março de 13
Se a ordem e os tipos forem mantidos, isso significa que quando eu falar em “pointlist” no Ruby, estarei acessando o pedacinho de memória que se refere a “pointlist” no C.
E agora, uma classe em Ruby arrematando a parada toda.
domingo, 3 de março de 13
class DelaunayTriangulator attr_reader :points def initialize(points) @points = points end
def triangulate point_array_ptr = FFI::MemoryPointer.new(:double, points.size * 2) point_array_ptr.write_array_of_double(flatten(points))
input = TriangulateIO.new output = TriangulateIO.new
input[:pointlist] = point_array_ptr input[:numberofpoints] = points.size input[:numberofpointattributes] = 0
TriangleFFI.triangulate 'czeXQ', input, output, nil
read_triangles_from output ensure free input free output end
# ...end
domingo, 3 de março de 13
point_array_ptr = FFI::MemoryPointer.new(:double, points.size * 2)point_array_ptr.write_array_of_double(flatten(points))
input[:pointlist] = point_array_ptrinput[:numberofpoints] = points.sizeinput[:numberofpointattributes] = 0
# ...
# ...
# ...Libera toda memória alocada em Ruby no próximo ciclo de
GC, a não ser que você especifique algo diferente.
domingo, 3 de março de 13
Aqui acontece a única parte bem FFI mesmo: eu tenho que alocar um ponteiro que receberá meu array de pontos para passar ao C, e preciso colocar isso no meu TriangulateIO em “input”.
# ... ensure free input free output end
Como há memória alocada no C, eu tenho que liberá-la.
domingo, 3 de março de 13
Como a biblioteca estipula que eu tenho que liberar toda a memória que o algoritmo por ventura alocar, adiciono um bloco ensure no final que chama DelaunayTriangulator#free em ambas as structs e cuida disso. Existe outra técnica (via ManagedStruct) que eliminaria esta seção.
def read_triangles_from(triangulateio) triangle_count = triangulateio[:numberoftriangles] triangle_indices = triangulateio[:trianglelist].read_array_of_int(triangle_count * 3)
triangles = []
0.step(triangle_indices.size - 1, 3) do |first_point_index| p0 = points[triangle_indices[first_point_index]] p1 = points[triangle_indices[first_point_index + 1]] p2 = points[triangle_indices[first_point_index + 2]] triangles << Triangle.new(p0, p1, p2) end
trianglesend
domingo, 3 de março de 13
Aqui transformo os triângulos em “output” em instâncias de Triangle dentro do meu domínio. “trianglelist” é uma lista de índices apontado para o array original de pontos.
Não dói muito, e você só precisa saber um tiquinho de C.
domingo, 3 de março de 13
É tranquilo. Você precisa saber o que é um ponteiro e que cada biblioteca tem alguma particularidade no gerenciamento de memória (apesar de, no C, haver um quase consenso de que quem usa uma biblioteca aloca e libera os buffers necessários/criados).
DEMO
domingo, 3 de março de 13
@dodecaphonic
domingo, 3 de março de 13
É isso. Obrigado pela atenção, e espero que tenha sido útil de alguma maneira.
PERGUNTAS
domingo, 3 de março de 13
http://github.com/dodecaphonic/mesherator
http://www.cs.cmu.edu/~quake/triangle.html
http://en.wikipedia.org/wiki/Delaunay_triangulation
domingo, 3 de março de 13