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
Subscribe to my newsletter
Read articles from Lucas Náiade directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by