Flutter + Golang : código nativo direto da fonte

Flutter + Golang : código nativo direto da fonte

Introdução

Depois de contribuir para um package da comunidade(por ex. flutter_zxing) tive a curiosidade de me aprofundar mais em como chamar código nativo direto de um projeto Dart, e também estou tendo cada vez mais interesses em linguagens como Rust e Golang, elas me atraem como o mel atraem ursos 🍯.

Então me propus a usar Golang interpolado com Dart e trouxe aqui um pequeno guia para você desenvolvedor e sofredor.

Antes de tudo é bom ter o Golang corretamente configurado em seu ambiente de desenvolvimento, junto também o C.

Golang com Dart 🤝

Escrevi um código Golang básico que calcula a sequência fibonacci. Note para a importação import "C", o cgo é o responsável pela ajuda de chamar código C com Golang.

package main

import "C"

//export fibonacci
func fibonacci(n C.uint) C.uint {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {}

Indicamos a função a ser exportada usando o comentário //export nomefuncao como documentado aqui

Para usar o ffigen devemos gerar o build e ter no minimo o arquivo de cabeçalho C e o arquivo de objeto compartilhado, os famosos *.so compilados. Como vou usar o flutter no Android, usei o comando:

CGO_ENABLED=1 GOOS="android" GOARCH="amd64" GOARM="" go build -buildmode=c-shared -o lib.so fibonacci.go
// para ver as possibilidades rode o comando `$ go tool dist list`

Talvez você precise apontar para para o NDK adicionando CC=meu caminhoparandk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang

Criando um projeto Flutter e tendo adicionado o ffigen as dependências de desenvolvimento podemos através do arquivo de cabeçalho gerar nosso código Dart para interpolar tudo isso.

flutter pub add -d ffigen

Para configurar o ffigen basta seguir a documentação aqui. Nesse nosso projeto adicionamos o seguinte ao final do pubspec.yaml:

# Meu sistema é um Linux Fedora
ffigen:
  name: "FibonacciGO"
  description: "Bindings to FibonacciGO"
  output: "lib/generated_bindings.dart"
  llvm-path:
    - "/usr/local/opt/llvm"
    - 'C:\Program Files\llvm'
    - "/usr/lib/llvm-11"
    - "/usr/lib64/libclang.so"
  headers:
    entry-points:
      - "lib.h"

Para gerar os binds basta executar o comando abaixo e o arquivo indicado no pubspec.yaml será gerado

flutter pub run ffigen

E para facilitar criei um arquivo para facilitar o bind

import 'dart:ffi';
import 'dart:io';

import 'package:fluttergo/generated_bindings.dart';

final FibonacciGO fibonacciGO = FibonacciGO(dylib);

DynamicLibrary _openDynamicLibrary() {
  if (Platform.isAndroid) {
    return DynamicLibrary.open('lib.so');  
  } else if (Platform.isLinux) {
    return DynamicLibrary.open('/dev/blog/fluttergo/lib.so');
  } else if (Platform.isWindows) {
    return DynamicLibrary.open('lib.dll');
  }
  return DynamicLibrary.process();
}

DynamicLibrary dylib = _openDynamicLibrary();

Caso tenha gerado os arquivos *.so para Android copie e cole cada um na respectiva pasta da arquitetura

  • android/app/src/main/jniLibs/arm64-v8a
  • android/app/src/main/jniLibs/armeabi-v7a
  • android/app/src/main/jniLibs/x86
  • android/app/src/main/jniLibs/x86_64

Performance 🏇

Criei uma função com a mesma implementação fibonacci em Dart. Usei o benchmark_harness para medir o desempenho Dart x Golang interpolado obtive o mesmo desempenho.

int fibonacci(int n) => n <= 2 ? 1 : fibonacci(n - 2) + fibonacci(n - 1);

Porém otimizando o código Golang o trem decolou

//export fibonacciSequential
func fibonacciSequential(n C.uint) C.uint {
    if n <= 1 {
        return C.uint(n)
    }

    var n2, n1 C.uint = 0, 1

    for i := C.uint(2); i < n; i++ {
        n2, n1 = n1, n1+n2
    }

    return n2 + n1
}

flutter: Run(RunTime): 388.61892877711654 us. //Dart

flutter: Run(RunTime): 2.5107325 us. //Golang

*µs => microsegundos

O código abaixo alcançou flutter: Run(RunTime): 0.3191476466012018 us.. Em contra partida não tentei mais otimizar o código Golang.

int fibonacciInLoops(int n) {
  if (n < 2) return n;
  var data = [0, 1];
  for (var i = 2; i < n + 1; i++) {
    data.add(data[i - 1] + data[i - 2]);
  }
  return data[data.length - 1];
}

Isso quer dizer que é muito mais provável que somente seja viável usar interpolação caso precise de grande poder computacional, grande volume de dados a ser tratados, algum código nativo especifico ou alguma solução em outra linguagem.

Considerações ✍️

Caso precise de um poder computacional grande talvez seja necessário interpolar com código nativo usando Golang, Rust, Java ou até C puro - tratamento de imagens e vídeos, criptografia, compressão de arquivos, IA, etc.

Repositório para você conferir

Fontes

Did you find this article valuable?

Support Lucas Náiade's blog by becoming a sponsor. Any amount is appreciated!