FFI - História, performance e felicidade com Ruby

432 visualizações

Publicada em

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

Publicada em: Tecnologia
0 comentários
1 gostou
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
432
No SlideShare
0
A partir de incorporações
0
Número de incorporações
1
Ações
Compartilhamentos
0
Downloads
1
Comentários
0
Gostaram
1
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

FFI - História, performance e felicidade com Ruby

  1. 1. FFI História, performance e felicidade com Ruby Vitor Capeladomingo, 3 de março de 13
  2. 2. @dodecaphonicdomingo, 3 de março de 13Sou @dodecaphonic no Twitter. Fico com essa cara quando o computador não faz o que eumando.
  3. 3. Meu trabalho às vezes exige que eu transforme isto...domingo, 3 de março de 13Eu trabalho com geoprocessamento. Isso envolve tanto fazer coisas babacas com GoogleMaps como transformar uma nuvem de pontos de um levantamento a laser...
  4. 4. ... nisto.domingo, 3 de março de 13... em uma malha de triângulos. Essa malha pode ser um terreno em que algo vai serconstruído, pode ser o levantamento do relevo para um estudo hidrológico, pode ser umscan para modelagem de um projeto de reconstrução.
  5. 5. A gente ama Ruby. O Ruby ama a gente também. ❤domingo, 3 de março de 13E eu gosto de fazer isso com Ruby. É muito gratificante dar uma solução sucinta, ou entãobem flexível, a problemas que em linguagens tradicionais do mercado seriam enfadonhos. Háoito anos ele é meu canivete suíço.
  6. 6. A gente ama Ruby. O Ruby ama a gente também. ❤domingo, 3 de março de 13E eu gosto de fazer isso com Ruby. É muito gratificante dar uma solução sucinta, ou entãobem flexível, a problemas que em linguagens tradicionais do mercado seriam enfadonhos. Háoito anos ele é meu canivete suíço.
  7. 7. Mas às vezes o amor não resolve.domingo, 3 de março de 13Sendo muito honesto, no entanto, nem sempre é a solução ideal.
  8. 8. 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, desoluções que tomem o mínimo possível de tempo.
  9. 9. 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, desoluções que tomem o mínimo possível de tempo.
  10. 10. 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, desoluções que tomem o mínimo possível de tempo.
  11. 11. Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante. “Não é verdade: você precisa apenas de algoritmos melhores!” E se eu já tiver tentado ? “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, desoluções que tomem o mínimo possível de tempo.
  12. 12. Vamos combinar, gente: tem vezes que o Ruby não é rápido o bastante. “Não é verdade: você precisa apenas de algoritmos melhores!” E se eu já tiver tentado ? “Quem se importa? 99% dos problemas se resumem a IO.” 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, desoluções que tomem o mínimo possível de tempo.
  13. 13. “Então vá programar em <Python| Java|Scala|Clojure|Haskell...>!”domingo, 3 de março de 13Já ouvi e já disse para mim mesmo algumas vezes: vá para outras linguagens. E já fui: resolvoe resolvi coisas com C++, aplico Scala aqui e ali, faço o que for preciso se o Ruby não derconta.
  14. 14. EU AMO o Ruby. A gente ama O Ruby ama a MIM também. gente ❤domingo, 3 de março de 13Mas isso não muda o fato de que quero usá-lo sempre que possível.
  15. 15. EU AMO o Ruby. A gente ama O Ruby ama a MIM também. gente ❤domingo, 3 de março de 13Mas isso não muda o fato de que quero usá-lo sempre que possível.
  16. 16. 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 13Com isso em mente, sempre que um problema aperta eu tento primeiro ir para o JRuby (cominvokedynamic ligado); se ainda assim não for o bastante, meu (nosso) melhor amigo ainda éo C.
  17. 17. RUBY & Cdomingo, 3 de março de 13
  18. 18. 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çãoa ele. Que todos os nossos problemas são novos ou únicos de alguma maneira. Os barbudosnos laboratórios escuros espalhados pelo mundo inventaram a Internet e o Unix enquanto agente nem pensava em nascer.
  19. 19. Isso significa que há bibliotecas às pencas, várias extremamente maduras e mantidas há décadas.domingo, 3 de março de 13O legado (o BOM legado) é imenso.
  20. 20. Ainda não há uma gem para resolver qualquer parada.domingo, 3 de março de 13Por mais que milhares de gems pipoquem a cada mês, não há solução para tudo. Encontroisso todos os dias no meu trabalho.
  21. 21. 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 13E para aproveitar esse legado, a melhor coisa atualmente é usar o ruby-ffi.
  22. 22. FOREIGN FUNCTION INTERFACE Java JNI .NET P/Invoke Python ctypes Mobile JS PhoneGapdomingo, 3 de março de 13O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito deligar duas linguagens díspares de modo que uma possa fazer uso da outra.
  23. 23. FOREIGN FUNCTION INTERFACE Java JNI .NET P/Invoke Python ctypes Mobile JS PhoneGap Rubydomingo, 3 de março de 13O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito deligar duas linguagens díspares de modo que uma possa fazer uso da outra.
  24. 24. FOREIGN FUNCTION INTERFACE Java JNI .NET P/Invoke Python ctypes Mobile JS PhoneGap Rubydomingo, 3 de março de 13O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito deligar duas linguagens díspares de modo que uma possa fazer uso da outra.
  25. 25. FOREIGN FUNCTION INTERFACE Java JNI .NET P/Invoke Python ctypes Mobile JS PhoneGap Ruby ruby-ffidomingo, 3 de março de 13O que é uma FFI, afinal? Já falei disso algumas vezes sem dar uma definição. FFI é um jeito deligar duas linguagens díspares de modo que uma possa fazer uso da outra.
  26. 26. Uma FFI permite que você chame funções em uma outra linguagem.domingo, 3 de março de 13
  27. 27. “Mas qual é a diferença de escrever uma extensão em C?”domingo, 3 de março de 13
  28. 28. domingo, 3 de março de 13Não é preciso saber programar em C, ou TER que programar em C, para poder usar umabiblioteca. Isso significa também que você não precisa compilar nada (o que é especialmentebom no Windows).
  29. 29. Você só precisa escrever Ruby.domingo, 3 de março de 13Não é preciso saber programar em C, ou TER que programar em C, para poder usar umabiblioteca. Isso significa também que você não precisa compilar nada (o que é especialmentebom no Windows).
  30. 30. 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
  31. 31. 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 13Seu resultado não fica restrito ao MRI, nem sujeito às idiossincrasias das camadas deadaptação de extensões C que o JRuby e Rubinius oferecem.
  32. 32. 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
  33. 33. É muito fácil de usar.domingo, 3 de março de 13
  34. 34. MESHERATORdomingo, 3 de março de 13Para ilustrar o ganho que uma biblioteca em C pode trazer a um projeto, escrevi um pequenodemo. “MESHERATOR” - MESH GENERATOR, um gerador de malhas trianguladas. Muitocriativo.
  35. 35. Transformar isto...domingo, 3 de março de 13E se vocês se lembram do começo da apresentação, meu objetivo era sair disto...
  36. 36. ... nisto.domingo, 3 de março de 13... para isto.
  37. 37. Este cara sabe como triangular nuvens de pontos muito rapidamente. JONATHAN SHEWCHUCK Sua biblioteca, por não ser em Ruby, não tem um nome criativo ou engraçadinho: é TRIANGLE, mesmo.domingo, 3 de março de 13
  38. 38. DEMOdomingo, 3 de março de 13
  39. 39. A API é bem pequena. void triangulate(char *, struct triangulateio *, struct triangulateio *, struct triangulateio *); void trifree(VOID *memptr); Uma string com as opções. A estrutura de entrada e saída do algoritmo.domingo, 3 de março de 13Nã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 emtriângulos; “trifree” permite que eu libere a memória alocada pelo algoritmo.
  40. 40. 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 */ triangle.c:19 - #define REAL double 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 */ };domingo, 3 de março de 13A estrutura é bem compreensível e descritiva (supondo que você conheça o domínio). Ostipos de dados também. A única coisa pouco familiar é “REAL” — que, olhando no código, éapenas um double.
  41. 41. Vamos começar definindo nosso encapsulamento com FFI.domingo, 3 de março de 13
  42. 42. 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 end enddomingo, 3 de março de 13Aqui defino então as duas funções. “attach_function” procura uma função de mesmo nomena biblioteca definida em “ffi_lib”. Os valores no array são os tipos de dados que a funcãorecebe; o último argumento é o retorno. Como ambas não retornam nada (operamdiretamente em ponteiros passados para elas), declaro como :void.
  43. 43. Uma struct básica é mapeada com FFI::Struct.domingo, 3 de março de 13
  44. 44. class TriangulateIO < ::FFI::Struct layout :pointlist, :pointer, :pointattributelist, :pointer, :pointmarkerlist, :pointer, Todo ponteiro (*) vira :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, :int enddomingo, 3 de março de 13Então eis a estrutura análoga em Ruby. Mantive os mesmos nomes, exatamente, para facilitaro entendimento, mas não é obrigatório: o mais importante é manter a ordem dos tipos dedados.
  45. 45. layout garante que o bloco de memória criado em C se encaixará como uma luva.domingo, 3 de março de 13Se a ordem e os tipos forem mantidos, isso significa que quando eu falar em “pointlist” noRuby, estarei acessando o pedacinho de memória que se refere a “pointlist” no C.
  46. 46. E agora, uma classe em Ruby arrematando a parada toda.domingo, 3 de março de 13
  47. 47. 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 # ... enddomingo, 3 de março de 13
  48. 48. # ... point_array_ptr = FFI::MemoryPointer.new(:double, points.size * 2) point_array_ptr.write_array_of_double(flatten(points)) # ... input[:pointlist] = point_array_ptr input[:numberofpoints] = points.size input[: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 13Aqui 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”.
  49. 49. # ... ensure free input Como há memória alocada no free output C, eu tenho que liberá-la. enddomingo, 3 de março de 13Como a biblioteca estipula que eu tenho que liberar toda a memória que o algoritmo porventura alocar, adiciono um bloco ensure no final que chama DelaunayTriangulator#free emambas as structs e cuida disso. Existe outra técnica (via ManagedStruct) que eliminaria estaseção.
  50. 50. 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 triangles enddomingo, 3 de março de 13Aqui 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.
  51. 51. 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 algumaparticularidade no gerenciamento de memória (apesar de, no C, haver um quase consenso deque quem usa uma biblioteca aloca e libera os buffers necessários/criados).
  52. 52. DEMOdomingo, 3 de março de 13
  53. 53. @dodecaphonicdomingo, 3 de março de 13É isso. Obrigado pela atenção, e espero que tenha sido útil de alguma maneira.
  54. 54. PERGUNTASdomingo, 3 de março de 13
  55. 55. http://github.com/dodecaphonic/mesherator http://www.cs.cmu.edu/~quake/triangle.html http://en.wikipedia.org/wiki/Delaunay_triangulationdomingo, 3 de março de 13

×