FFI - História, performance e felicidade com Ruby

55
FFI História, performance e felicidade com Ruby Vitor Capela domingo, 3 de março de 13

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.

Transcript of FFI - História, performance e felicidade com Ruby

Page 1: FFI - História, performance e felicidade com Ruby

FFI História, performance e felicidade com Ruby

Vitor Capela

domingo, 3 de março de 13

Page 2: FFI - História, performance e felicidade com Ruby

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

Page 3: FFI - História, performance e felicidade com Ruby

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

Page 4: FFI - História, performance e felicidade com Ruby

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

Page 5: FFI - História, performance e felicidade com Ruby

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.

Page 6: FFI - História, performance e felicidade com Ruby

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.

Page 7: FFI - História, performance e felicidade com Ruby

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.

Page 8: FFI - História, performance e felicidade com Ruby

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.

Page 9: FFI - História, performance e felicidade com Ruby

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.

Page 10: FFI - História, performance e felicidade com Ruby

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.

Page 11: FFI - História, performance e felicidade com Ruby

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.

Page 12: FFI - História, performance e felicidade com Ruby

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.

Page 13: FFI - História, performance e felicidade com Ruby

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

Page 14: FFI - História, performance e felicidade com Ruby

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.

Page 15: FFI - História, performance e felicidade com Ruby

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.

Page 16: FFI - História, performance e felicidade com Ruby

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.

Page 17: FFI - História, performance e felicidade com Ruby

RUBY & Cdomingo, 3 de março de 13

Page 18: FFI - História, performance e felicidade com Ruby

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.

Page 19: FFI - História, performance e felicidade com Ruby

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.

Page 20: FFI - História, performance e felicidade com Ruby

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.

Page 21: FFI - História, performance e felicidade com Ruby

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.

Page 22: FFI - História, performance e felicidade com Ruby

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.

Page 23: FFI - História, performance e felicidade com Ruby

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.

Page 24: FFI - História, performance e felicidade com Ruby

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.

Page 25: FFI - História, performance e felicidade com Ruby

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.

Page 26: FFI - História, performance e felicidade com Ruby

Uma FFI permite que você chame funções em uma outra linguagem.

domingo, 3 de março de 13

Page 27: FFI - História, performance e felicidade com Ruby

“Mas qual é a diferença de escrever uma extensão em C?”

domingo, 3 de março de 13

Page 28: FFI - História, performance e felicidade com 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).

Page 29: FFI - História, performance e felicidade com Ruby

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

Page 30: FFI - História, performance e felicidade com Ruby

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

Page 31: FFI - História, performance e felicidade com Ruby

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.

Page 32: FFI - História, performance e felicidade com Ruby

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

Page 33: FFI - História, performance e felicidade com Ruby

É muito fácil de usar.

domingo, 3 de março de 13

Page 34: FFI - História, performance e felicidade com Ruby

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.

Page 35: FFI - História, performance e felicidade com Ruby

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

Page 36: FFI - História, performance e felicidade com Ruby

... nisto.

domingo, 3 de março de 13

... para isto.

Page 37: FFI - História, performance e felicidade com Ruby

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

Page 38: FFI - História, performance e felicidade com Ruby

DEMO

domingo, 3 de março de 13

Page 39: FFI - História, performance e felicidade com Ruby

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.

Page 40: FFI - História, performance e felicidade com Ruby

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.

Page 41: FFI - História, performance e felicidade com Ruby

Vamos começar definindo nosso encapsulamento com FFI.

domingo, 3 de março de 13

Page 42: FFI - História, performance e felicidade com Ruby

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.

Page 43: FFI - História, performance e felicidade com Ruby

Uma struct básica é mapeada com FFI::Struct.

domingo, 3 de março de 13

Page 44: FFI - História, performance e felicidade com Ruby

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.

Page 45: FFI - História, performance e felicidade com Ruby

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.

Page 46: FFI - História, performance e felicidade com Ruby

E agora, uma classe em Ruby arrematando a parada toda.

domingo, 3 de março de 13

Page 47: FFI - História, performance e felicidade com Ruby

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

Page 48: FFI - História, performance e felicidade com Ruby

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

Page 49: FFI - História, performance e felicidade com Ruby

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

Page 50: FFI - História, performance e felicidade com Ruby

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.

Page 51: FFI - História, performance e felicidade com Ruby

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

Page 52: FFI - História, performance e felicidade com Ruby

DEMO

domingo, 3 de março de 13

Page 53: FFI - História, performance e felicidade com Ruby

@dodecaphonic

domingo, 3 de março de 13

É isso. Obrigado pela atenção, e espero que tenha sido útil de alguma maneira.

Page 54: FFI - História, performance e felicidade com Ruby

PERGUNTAS

domingo, 3 de março de 13