À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.
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
FFI - História, performance e felicidade com Ruby
1. FFI História, performance e
felicidade com Ruby
Vitor Capela
domingo, 3 de março de 13
2. @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.
3. 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...
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 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.
5. 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.
6. 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.
7. 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.
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, de
soluções que tomem o mínimo possível de tempo.
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, de
soluções que tomem o mínimo possível de tempo.
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, de
soluções que tomem o mínimo possível de tempo.
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, de
soluções que tomem o mínimo possível de tempo.
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, de
soluções que tomem o mínimo possível de tempo.
13. “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.
14. EU AMO o Ruby.
A gente ama
O Ruby ama a MIM também.
gente
❤
domingo, 3 de março de 13
Mas isso não muda o fato de que quero usá-lo sempre que possível.
15. EU AMO o Ruby.
A gente ama
O Ruby ama a MIM também.
gente
❤
domingo, 3 de março de 13
Mas isso não muda o fato de que quero usá-lo sempre que possível.
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 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.
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çã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.
19. 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.
20. 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.
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 13
E para aproveitar esse legado, a melhor coisa atualmente é usar o ruby-ffi.
22. FOREIGN
FUNCTION
INTERFACE
Java JNI
.NET P/Invoke
Python ctypes
Mobile JS PhoneGap
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.
23. FOREIGN
FUNCTION
INTERFACE
Java JNI
.NET P/Invoke
Python ctypes
Mobile JS PhoneGap
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.
24. FOREIGN
FUNCTION
INTERFACE
Java JNI
.NET P/Invoke
Python ctypes
Mobile JS PhoneGap
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.
25. FOREIGN
FUNCTION
INTERFACE
Java JNI
.NET P/Invoke
Python ctypes
Mobile JS PhoneGap
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.
26. Uma FFI permite que você chame funções
em uma outra linguagem.
domingo, 3 de março de 13
27. “Mas qual é a diferença de escrever uma
extensão em C?”
domingo, 3 de março de 13
28. 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).
29. 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).
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. 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.
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
34. 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.
35. 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...
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
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 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.
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 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.
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
end
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.
43. Uma struct básica é mapeada com
FFI::Struct.
domingo, 3 de março de 13
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
end
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.
45. 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.
46. E agora, uma classe em Ruby
arrematando a parada toda.
domingo, 3 de março de 13
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
# ...
end
domingo, 3 de março de 13
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 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”.
49. # ...
ensure
free input Como há memória alocada no
free output C, eu tenho que liberá-la.
end
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.
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
end
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.
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 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).