Pipeline de liquidación de cierre de día

Gana FxDart async

Requisito

Cierra el día. A partir de diez transacciones con tarjeta (en el código de abajo): descarta las failed, agrupa el resto por comercio y calcula el neto de cada comercio (los reembolsos van en negativo). Publica la liquidación de cada comercio en la pasarela bancaria — como mucho dos publicaciones en vuelo, con los resultados en orden de comercio — y luego imprime el informe: una línea por comercio, un desglose entre pagos y cobros (los reembolsos de un comercio superan sus cargos), el total general y la prueba del máximo en vuelo.

Esta es la librería entera en un solo pipeline. Preparación síncrona: rejectgroupBysumBy por grupo → sortBy. Cruza a asíncrono con toAsync y publica bajo concurrent(2). El informe usa de nuevo partition y sumBy.

Salida esperada
2026-07-27 close — 3 merchants, 2 postings at a time:
  BookNook: 3 txns, net $-6.01
  Cafe Luna: 4 txns, net $32.00
  GadgetHub: 2 txns, net $218.90
payouts: 2, collections due: 1
settled: $244.89
max postings in flight: 2

Lado a lado

Dart nativo

FxDart

Por qué difieren

Cada mitad de esta tarea ya ha aparecido en un ejemplo más pequeño; lo interesante aquí es qué ocurre cuando se juntan. Dart nativo resuelve la preparación bastante bien con package:collection (groupListsBy, sortedBy) — aunque sacar el neto de cada grupo es un fold con un valor inicial explícito, y el desglose de pagos son dos pasadas de where. Después llega la frontera asíncrona y la forma se rompe: la publicación acotada necesita el pool de workers, una función aparte con nombre, con slots y un cursor, y el pipeline que venías leyendo se convierte en fontanería que tienes que seguir a mano. La versión con FxDart es una única cadena ininterrumpida desde las transacciones en bruto hasta las liquidaciones publicadas — catorce líneas en las que la política (qué es válido, cómo agrupar, con cuánta fuerza golpear la pasarela) es el texto visible, y la mecánica es problema de la librería.

Benchmark

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

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

Dart nativo 38 µs
FxDart 35 µs

Memoria pico Empate

Dart nativo 16.5 MB
FxDart 16.7 MB

N = 10,000

Tiempo Gana FxDart

Dart nativo 4.21 ms
FxDart 2.97 ms

Memoria pico Gana nativo

Dart nativo 23.1 MB
FxDart 24.4 MB

N = 100,000

Tiempo Gana FxDart

Dart nativo 48.9 ms
FxDart 30.7 ms

Memoria pico Empate

Dart nativo 56.6 MB
FxDart 58.2 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í.