Descargas en paralelo, resultados en orden
Requisito
Descarga seis archivos — cada uno con un tamaño fijo y un tiempo de
transferencia fijo (simulado), en el código de abajo — con como mucho
tres descargas en vuelo, y lista los resultados
numerados en el orden en que se pidieron. Los
retardos están elegidos para que las finalizaciones se entrelacen: el
video.mp4 de 30 ms se pide primero, pero el
notes.txt de 10 ms termina antes. Ambas versiones
imprimen qué archivo terminó primero y la concurrencia máxima
observada — la prueba de que el trabajo se solapó de verdad, fuera de
orden, mientras el listado se mantenía en orden.
Esa garantía de reordenar por dentro y conservar el orden en la
superficie es justo lo que hace concurrent(3): evalúa
hasta tres elementos aguas arriba a la vez y aun así entrega los
resultados en el orden de la fuente. La cadena los numera con
zipWithIndex y monta el informe con join.
Salida esperada
downloaded 6 files, 3 at a time, in request order: 1. video.mp4 (900 KB) 2. notes.txt (2 KB) 3. slides.pdf (340 KB) 4. photo.jpg (180 KB) 5. data.csv (55 KB) 6. theme.zip (260 KB) first to finish: notes.txt total size: 1737 KB max downloads in flight: 3
Lado a lado
Dart nativo
FxDart
Por qué difieren
Future.wait conserva el orden, pero descarga todo a la
vez — sin límite. Añadir el límite es lo que obliga a montar el pool
de workers nativo, y conservar el orden bajo ese pool es precisamente
la parte sutil: la lista results predimensionada e
indexada por un cursor compartido. Si te equivocas en la contabilidad
de las posiciones, los resultados vuelven desordenados — un bug que
solo se manifiesta cuando el orden de finalización resulta ser
distinto del orden de petición, algo que depende de los tiempos y es
fácil que se escape en los tests. En FxDart la garantía de orden es el
contrato del operador, no tu código: concurrent(3) no
puede devolver elementos desordenados, caigan como caigan los tiempos.
Benchmark
Caso async: la escala principal es N = 100,000, no 1,000,000. Cada elemento cuesta una vuelta del event loop en ambos lados, así que un millón de awaits reales mediría el event loop de Dart durante minutos — no el pipeline. Los retardos son de longitud cero y se conserva el límite de concurrencia del ejemplo; lo que comparan las barras es la maquinaria del pipeline.
N = 100
Tiempo Empate
Memoria pico Empate
N = 10,000
Tiempo Gana nativo
Memoria pico Gana FxDart
N = 100,000
Tiempo Gana nativo
Memoria pico Gana nativo
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í.