Saltar al contenido
CrossFly es 100% open sourceSíguenos en GitHub
CrossFlyCrossFly
Empezar
Benchmark

Cinco frameworks, el mismo trabajo, un teléfono

Catorce fases, reimplementadas escena por escena en Flutter, React Native, .NET MAUI y Kotlin Multiplatform, ejecutadas de principio a fin sin un solo toque. Cada número de esta página salió de un dispositivo; ninguno fue elegido.

Medido en Redmi Note 10 Prosweet, Adreno 618, 1080×2400, 120 Hz, Android 123 iteraciones por fase
CrossFly CoreCrossFly Sync

Lee esto primero

Las tablas de abajo no se leen sin esto. Viene de la documentación del propio benchmark, no de nosotros.

Un teléfono, una versión de la especificación

Todo aquí se midió en un Redmi Note 10 Pro (sweet, Adreno 618, 1080×2400, 120 Hz, Android 12), builds de release, 3 iteraciones por fase y un arranque de la app por iteración. Es una comparación entre cinco frameworks en un dispositivo, no una afirmación sobre ningún otro.

Las filas a 120 fps son suelos, no resultados

La pantalla va a 120 Hz y varias escenas tocan ese techo en más de un framework. Una fila en el techo dice «este framework no era el límite aquí» y nada más; separarlas exige una carga mayor, que es para lo que existe CFBENCH_LOAD.

La tasa de fotogramas es la del propio app

Flashlight cuenta la ventana del app siendo presentada, lo que en cuatro de las cinco plataformas está desacoplado del costo de la escena: leyó exactamente 60,00 para una fase de React Native que no dibuja nada, y nunca superó 60,00 en 3388 muestras en una pantalla de 120 Hz. Así que los fotogramas son lo que cada app midió de sí misma; lo que aporta Flashlight es CPU y memoria, que ninguna app mide sobre sí.

Tasa de fotogramas, fase por fase

La medición que cada app hace de sí misma: en cuatro de las cinco plataformas un profiler externo mide la ventana, no la escena. Más es mejor.

FaseCrossFlyFlutterReact NativeKotlin Multiplatform.NET MAUI
draw_rect
16 000 filled rects in a full-screen grid
104.99.310.357.734.7
draw_text
16 000 short text runs
55.26.713.99.98.0
draw_image
16 000 image quads from one texture
87.97.612.735.227.8
cards
as many realistic cards as fit at a fixed font scale
118.8en el techo49.075.4103.174.0
textfield
2400 real, interactive TextFields
56.412.80.0617.918.7
image_one_texture
a 48×48 quad grid from one atlas page
118.5en el techo53.972.6119.7en el techo120.0en el techo
shapes_lines
a batched line every 1px across the surface
111.429.3120.1en el techo119.8en el techo119.9en el techo
shapes_circles
50 concentric filled circles
99.140.9107.3112.9105.1
shapes_polygon_concave
3 nested 36-point concave stars
118.6en el techo119.2en el techo89.0117.287.4
shapes_polygon_convex
3 nested 36-side convex polygons
119.6en el techo119.3en el techo120.0en el techo120.0en el techo120.0en el techo
shapes_shadow
8 nested rounded rects with a 40px blurred shadow
119.9en el techo110.2119.4en el techo120.0en el techo120.0en el techo
shapes_text
16 offset passes over a screenful of text
80.346.3117.673.644.1
sqlite_validate
that the encryption below is real
11/11 pass7/7 pass3/5 passno implementado7/7 pass
sqlite_bench
100k rows × {plain, ChaCha20, SQLCipher}
3 ciphers2 ciphers2 ciphersno implementado2 ciphers

Las celdas sombreadas están en el techo de 120 Hz de la pantalla. Significan «este framework no era el límite aquí» y nada más: son suelos, no resultados.

La fila donde gana un port

shapes_circles — 50 círculos concéntricos rellenos, unos 35 megapíxeles de overdraw opaco — corre más rápido en tres de los cuatro ports. Es el mismo trabajo en ambos lados: todos son Skia sobre el driver OpenGL ES de esta GPU, su tiempo de fotograma es lineal en el área pintada igual que el de la referencia, y lo que separa es el precio del píxel. Allí la referencia está limitada por CPU en su propio teselador.

Es en la memoria donde los frameworks se separan

Pico de PSS en las catorce fases. El suelo es lo que cobra un framework antes de dibujar nada; el techo es lo que cuesta su peor escena.

CrossFly118–135 MB · 2400 campos de texto 132 MB
suelotecho
Kotlin Multiplatform121–227 MB · 2400 campos de texto 341 MB
suelotecho
React Native126–591 MB · 2400 campos de texto 601 MB
suelotecho
Flutter152–286 MB · 2400 campos de texto 556 MB
suelotecho
.NET MAUI174–203 MB · 2400 campos de texto 321 MB
suelotecho

El suelo es el runtime más el contexto GL: el precio de entrada, antes de un solo píxel.

La única escena donde todo port paga y la referencia no

2400 campos de texto nativos — EditText, TextInput, Entry, BasicTextField — cuestan entre 321 y 601 MB. CrossFly dibuja sus 2400 y paga 132 MB, 14 MB por encima de su propio suelo. El costo no es dibujar campos de texto; es tenerlos como objetos de la plataforma.

Lo que se entrega

Release, firmado, solo arm64-v8a: cualquier otra cosa compara convenciones de empaquetado, no frameworks.

Sin encoger — R8 apagado
CrossFly
5.95 MB
Flutter
22.65 MB
Kotlin Multiplatform
6.17 MB
React Native
29.69 MB
.NET MAUI
37.34 MB
Encogido — solo el código que cada app alcanza
CrossFly
2.66 MB−55%
Flutter
20.74 MB−8%
Kotlin Multiplatform
0.88 MB−86%
React Native
27.59 MB−7%
.NET MAUI
21.13 MB−43%

Las builds encogidas no son las que se midieron, salvo la de MAUI, que no puede construirse de otra forma. Y la tabla encogida no puede ordenarse tal cual: Kotlin Multiplatform no implementa las fases de SQLite, así que no lleva motor, mientras que el de la referencia son 1,82 MB de sus 2,66 MB. Restándolo, los dos quedan a 0,04 MB.

SQLite: la diferencia es la capa de binding

100 000 filas, el mismo SQL, el mismo journal mode y synchronous en todas las plataformas. Los tiempos de React Native quedan fuera porque su propia fase de validación falla dos de cinco comprobaciones, que es justo para lo que existe.

MediciónCrossFlyFlutterReact Native.NET MAUI
plain insert, 100 000 rows216 ms251 ms—1248 ms
rows/s463 000398 000—80 100
plain bulk insert154 ms232 ms—1242 ms
ciphers runplain, chacha20, sqlcipherplain, sqlcipherplain, sqlcipherplain, sqlcipher
validation11/117/73/57/7
Por qué es tan rápido

Menos capas entre tú y la GPU

Cada salto que da un fotograma cuesta tiempo. CrossFly dibuja directamente a través de LibGDX — sin puente de JavaScript que cruzar.

CrossFly
2 saltos
Tu código Java
Java puro, sin DSL
Motor LibGDX
Dibujo acelerado por GPU
GPU
Píxeles en pantalla
Flutter
2 saltos
Tu código Dart
Nuevo lenguaje que aprender
Motor Skia
Renderiza widgets
GPU
Píxeles en pantalla
React Native
3 saltos + puente
Tu código JS
Corre en un motor JS
El puente
Serializado — el cuello de botella
UI nativa
Vistas de plataforma
GPU
Píxeles en pantalla

Todo lo que matiza los números de arriba

Entero, no resumido. Un benchmark sin sus advertencias es un anuncio.

Un teléfono, una versión de la especificación

Todo aquí se midió en un Redmi Note 10 Pro (sweet, Adreno 618, 1080×2400, 120 Hz, Android 12), builds de release, 3 iteraciones por fase y un arranque de la app por iteración. Es una comparación entre cinco frameworks en un dispositivo, no una afirmación sobre ningún otro.

Las filas a 120 fps son suelos, no resultados

La pantalla va a 120 Hz y varias escenas tocan ese techo en más de un framework. Una fila en el techo dice «este framework no era el límite aquí» y nada más; separarlas exige una carga mayor, que es para lo que existe CFBENCH_LOAD.

La tasa de fotogramas es la del propio app

Flashlight cuenta la ventana del app siendo presentada, lo que en cuatro de las cinco plataformas está desacoplado del costo de la escena: leyó exactamente 60,00 para una fase de React Native que no dibuja nada, y nunca superó 60,00 en 3388 muestras en una pantalla de 120 Hz. Así que los fotogramas son lo que cada app midió de sí misma; lo que aporta Flashlight es CPU y memoria, que ninguna app mide sobre sí.

La CPU no se compara entre plataformas fila a fila

Una escena a 120 fps envía ocho veces más fotogramas que una a 14, así que su CPU es mayor por una razón que nada tiene que ver con la eficiencia. Divide por el número de fotogramas antes de comparar dos celdas.

Nada por debajo de un 3% es un hallazgo

Las filas draw_*, textfield y shapes_text varían esa cantidad entre ejecuciones sobre un build sin cambios, así que una diferencia menor es ruido.

Donde un port no implementa una fase, lo dice

Kotlin Multiplatform no tiene las fases de SQLite y reporta el hueco en vez de saltárselo. La validación de SQLite de React Native falla dos de cinco comprobaciones — que es la fase diciendo que los tiempos de al lado no son fiables — así que quedan fuera en vez de publicarse.

Ejecútalo tú

La app de benchmark, los cuatro ports y los scripts de medición son un solo repositorio. Unos doce minutos por plataforma.

flashlight/run-all.sh flashlight/results/crossfly dev.crossfly.benchmark
python flashlight/summarize.py flashlight/results/crossfly

Los números son una cosa. Ejecuta la cosa.

Treinta y ocho ejemplos corren en tu navegador, cada uno con su código completo al lado.

Abrir los ejemplosLeer la documentación