Los 3 gastos más grandes

Empate

Requisito

De un mes de gastos, imprime los tres más grandes — comercio e importe, de mayor a menor. Los datos están en el código de abajo; ambas versiones deben imprimir las líneas que aparecen bajo Salida esperada.

Salida esperada
Airline Ticket  $289.99
New Headphones  $129.00
Electric Co     $60.34

Lado a lado

Dart nativo

FxDart

Por qué difieren

Apenas difieren — esto es un empate. Ambos lados ordenan por una clave negada para conseguir orden descendente y toman los tres primeros; el sortedBy de package:collection es igual de directo que el sortBy de FxDart (el List.sort del núcleo, por sí solo, mutaría en el sitio y necesitaría un comparador explícito, pero collection es una dependencia estándar). La única diferencia real está en dónde vive el vocabulario: un método de extensión de un paquete frente a un paso de una cadena que además ofrece scan, chunk y variantes asíncronas. Elige cualquiera de los dos con la conciencia tranquila.

El listado empata; el reloj no. Con un millón de filas las barras de abajo tienen a FxDart unas 2,6× más rápido (200 ms frente a 521 ms), y no se debe a un big-O más listo — ambos copian la lista y ejecutan una mezcla estable O(n log n). La diferencia está en lo que cuesta una sola comparación.

El sortedBy nativo es el mergeSortBy de collection: ordena las filas y llama al extractor de clave dentro de cada comparación. Un millón de filas son unas veinte millones de llamadas a (t) => -t.amount. El tipo de clave es un K extends Comparable genérico — aquí num — así que cada una de esas claves es un double encajonado en el montículo comparado por un compareTo virtual.

El sortBy de FxDart extrae primero. Un recorrido de la lista escribe cada clave en un Float64List (vio que todas eran double). Luego mezcla las claves y las filas juntas, en orden: cada comparación son dos dobles de máquina de un array tipado, con una comparación de la VM en estos datos (los importes son positivos finitos ordinarios, así que no hay NaN ni -0.0 que fuerce el camino más lento de compareTo). Una extracción por fila, sin encajonar, sin despacho y sin perseguir claves por un índice al azar. Un sortBy anterior decoraba ordenando una lista de índices con List.sort; eso ya no está. La mezcla es estable por construcción — las claves iguales conservan su orden de entrada en ambos lados ahora.

El límite honesto: cuando las claves no son uniformemente double, int o String, sortBy recae en el comparador genérico y la ventaja desaparece. La memoria con un millón de filas está cerca (unos 123 MB frente a 130 MB) — ambos retienen la copia de filas más un búfer auxiliar. Aquí los importes son todos distintos, así que la estabilidad no se ve en las tres líneas impresas.

Benchmark

Apple M1 Max, 32 GB de RAM · Dart 3.12.2 (compilado AOT) · 2026-08-24

N = 100

Tiempo Empate

Dart nativo 20 µs
FxDart 5.6 µs

Memoria pico Empate

Dart nativo 16.6 MB
FxDart 16.5 MB

N = 10,000

Tiempo Gana FxDart

Dart nativo 3.31 ms
FxDart 846 µs

Memoria pico Gana FxDart

Dart nativo 23.2 MB
FxDart 21.4 MB

N = 1,000,000

Tiempo Gana FxDart

Dart nativo 521.1 ms
FxDart 199.6 ms

Memoria pico Gana nativo

Dart nativo 123.2 MB
FxDart 129.7 MB

Las barras son medianas de iteraciones cronometradas repetidas en procesos nuevos por lado (los N pequeños se agrupan por resolución del temporizador). Dos lados a menos del 5% entre sí — o a menos de 0.6 ms, una diferencia que nadie puede percibir — cuentan como empate; las carreras relativas ajustadas se vuelven a medir hasta 5 veces. En una app, cualquier cosa por debajo de unos pocos milisegundos es invisible para el usuario, gane la barra que gane. La memoria es el RSS pico del proceso. La VM de Dart y el dataset son idénticos en ambos lados, así que la diferencia entre las dos barras es lo que retiene el pipeline en sí.