Leia isto antes
As tabelas abaixo não se leem sem isto. Vem da documentação do próprio benchmark, não de nós.
Tudo aqui foi medido num Redmi Note 10 Pro (sweet, Adreno 618, 1080×2400, 120 Hz, Android 12), builds de release, 3 iterações por fase e uma abertura do app por iteração. É uma comparação entre cinco frameworks num aparelho, não uma afirmação sobre qualquer outro.
A tela roda a 120 Hz, e várias cenas encostam nesse teto em mais de um framework. Uma linha no teto diz "este framework não era o limite aqui" e nada mais; separá-las exige uma carga maior, que é para isso que existe o CFBENCH_LOAD do benchmark.
O Flashlight conta a janela do app sendo apresentada, o que em quatro das cinco plataformas está desacoplado do custo da cena — ele leu exatamente 60,00 para uma fase do React Native que não desenha nada, e nunca passou de 60,00 em 3388 amostras numa tela de 120 Hz. Então a taxa de quadros é a que cada app mediu de si mesmo; o que o Flashlight acrescenta é CPU e memória, que nenhum app mede sobre si.
Taxa de quadros, fase a fase
A medição que cada app faz de si mesmo — em quatro das cinco plataformas um profiler externo mede a janela, não a cena. Mais é melhor.
| Fase | CrossFly | Flutter | React Native | Kotlin Multiplatform | .NET MAUI |
|---|---|---|---|---|---|
draw_rect 16 000 filled rects in a full-screen grid | 104.9 | 9.3 | 10.3 | 57.7 | 34.7 |
draw_text 16 000 short text runs | 55.2 | 6.7 | 13.9 | 9.9 | 8.0 |
draw_image 16 000 image quads from one texture | 87.9 | 7.6 | 12.7 | 35.2 | 27.8 |
cards as many realistic cards as fit at a fixed font scale | 118.8no teto | 49.0 | 75.4 | 103.1 | 74.0 |
textfield 2400 real, interactive TextFields | 56.4 | 12.8 | 0.06 | 17.9 | 18.7 |
image_one_texture a 48×48 quad grid from one atlas page | 118.5no teto | 53.9 | 72.6 | 119.7no teto | 120.0no teto |
shapes_lines a batched line every 1px across the surface | 111.4 | 29.3 | 120.1no teto | 119.8no teto | 119.9no teto |
shapes_circles 50 concentric filled circles | 99.1 | 40.9 | 107.3 | 112.9 | 105.1 |
shapes_polygon_concave 3 nested 36-point concave stars | 118.6no teto | 119.2no teto | 89.0 | 117.2 | 87.4 |
shapes_polygon_convex 3 nested 36-side convex polygons | 119.6no teto | 119.3no teto | 120.0no teto | 120.0no teto | 120.0no teto |
shapes_shadow 8 nested rounded rects with a 40px blurred shadow | 119.9no teto | 110.2 | 119.4no teto | 120.0no teto | 120.0no teto |
shapes_text 16 offset passes over a screenful of text | 80.3 | 46.3 | 117.6 | 73.6 | 44.1 |
sqlite_validate that the encryption below is real | 11/11 pass | 7/7 pass | 3/5 pass | não implementado | 7/7 pass |
sqlite_bench 100k rows × {plain, ChaCha20, SQLCipher} | 3 ciphers | 2 ciphers | 2 ciphers | não implementado | 2 ciphers |
As células sombreadas estão no teto de 120 Hz da tela. Significam "este framework não era o limite aqui" e nada mais — são pisos, não resultados.
shapes_circles — 50 círculos concêntricos preenchidos, cerca de 35 megapixels de overdraw opaco — roda mais rápido em três dos quatro ports. É o mesmo trabalho dos dois lados: todos são Skia sobre o driver OpenGL ES desta GPU, o tempo de quadro deles é linear na área pintada exatamente como o da referência, e o que separa é o preço do pixel. Ali a referência está limitada pela CPU no próprio tesselador.
É na memória que os frameworks se separam
Pico de PSS nas quatorze fases. O piso é o que o framework cobra antes de desenhar qualquer coisa; o teto é o que a pior cena custa.
O piso é o runtime mais o contexto GL — o preço da entrada, antes de um único pixel.
2400 campos de texto nativos — EditText, TextInput, Entry, BasicTextField — custam entre 321 e 601 MB. O CrossFly desenha os 2400 sozinho e paga 132 MB, 14 MB acima do próprio piso. O custo não é desenhar campos de texto; é tê-los como objetos da plataforma.
O que é entregue
Release, assinado, só arm64-v8a — qualquer outra coisa compara convenções de empacotamento, não frameworks.
Os builds encolhidos não são os que foram medidos, exceto o do MAUI, que não pode ser construído de outro jeito. E a tabela encolhida não pode ser ranqueada como está: o Kotlin Multiplatform não implementa as fases de SQLite, então não leva engine nenhuma, enquanto a da referência é 1,82 MB dos seus 2,66 MB. Subtraindo, os dois ficam a 0,04 MB um do outro.
SQLite: a diferença é a camada de binding
100 000 linhas, o mesmo SQL, o mesmo journal mode e synchronous em todas as plataformas. Os tempos do React Native ficam de fora porque a fase de validação dele falha duas de cinco checagens — que é exatamente para isso que ela existe.
| Medição | CrossFly | Flutter | React Native | .NET MAUI |
|---|---|---|---|---|
| plain insert, 100 000 rows | 216 ms | 251 ms | — | 1248 ms |
| rows/s | 463 000 | 398 000 | — | 80 100 |
| plain bulk insert | 154 ms | 232 ms | — | 1242 ms |
| ciphers run | plain, chacha20, sqlcipher | plain, sqlcipher | plain, sqlcipher | plain, sqlcipher |
| validation | 11/11 | 7/7 | 3/5 | 7/7 |
Menos camadas entre você e a GPU
Cada salto que um quadro faz custa tempo. O CrossFly desenha direto pelo LibGDX — sem ponte JavaScript para atravessar.
Tudo o que qualifica os números acima
Inteiro, não resumido. Um benchmark sem as ressalvas é um anúncio.
Tudo aqui foi medido num Redmi Note 10 Pro (sweet, Adreno 618, 1080×2400, 120 Hz, Android 12), builds de release, 3 iterações por fase e uma abertura do app por iteração. É uma comparação entre cinco frameworks num aparelho, não uma afirmação sobre qualquer outro.
A tela roda a 120 Hz, e várias cenas encostam nesse teto em mais de um framework. Uma linha no teto diz "este framework não era o limite aqui" e nada mais; separá-las exige uma carga maior, que é para isso que existe o CFBENCH_LOAD do benchmark.
O Flashlight conta a janela do app sendo apresentada, o que em quatro das cinco plataformas está desacoplado do custo da cena — ele leu exatamente 60,00 para uma fase do React Native que não desenha nada, e nunca passou de 60,00 em 3388 amostras numa tela de 120 Hz. Então a taxa de quadros é a que cada app mediu de si mesmo; o que o Flashlight acrescenta é CPU e memória, que nenhum app mede sobre si.
Uma cena a 120 fps envia oito vezes mais quadros que uma a 14, então a CPU dela é maior por um motivo que nada tem a ver com eficiência. Divida pelo número de quadros antes de comparar duas células.
As linhas draw_*, textfield e shapes_text variam esse tanto entre corridas num build inalterado, então uma diferença menor que isso é ruído.
O Kotlin Multiplatform não tem as fases de SQLite e reporta a lacuna em vez de pulá-la. A validação de SQLite do React Native falha duas de cinco checagens — que é a fase dizendo que os tempos ao lado não são confiáveis — então eles ficam de fora em vez de serem publicados.
O app de benchmark, os quatro ports e os scripts de medição são um repositório só. Cerca de doze minutos por plataforma.
flashlight/run-all.sh flashlight/results/crossfly dev.crossfly.benchmark python flashlight/summarize.py flashlight/results/crossfly