Esta página ainda não foi traduzida, por isso é exibida em inglês. Ajude a traduzir

Top 3 largest expenses

Toss-up

Requirement

From a month of expenses, print the three largest — merchant and amount, biggest first. The data is in the code below; both versions must print the lines shown under Expected output.

Expected output
Airline Ticket  $289.99
New Headphones  $129.00
Electric Co     $60.34

Side by side

Native Dart

FxDart

Why they differ

They barely do — this one is a tie on the page. Both sides sort on a negated key to get descending order and take the first three; package:collection's sortedBy is every bit as direct as FxDart's sortBy (core List.sort alone would mutate in place and need an explicit comparator, but collection is a standard dependency). The only real difference in the listing is where the vocabulary lives: an extension method from a package vs a step in a chain that also offers scan, chunk, and async variants. Pick either with a clear conscience.

The listing is a tie; the clock is not. At a million rows the bars below have FxDart about 2.6× faster (200 ms vs 521 ms), and that is not a smarter big-O — both copy the list and run a stable O(n log n) merge sort. The difference is what one comparison costs.

Native sortedBy is collection's mergeSortBy: it sorts the rows and calls the key extractor inside every comparison. A million rows is about twenty million calls to (t) => -t.amount. The key type is a generic K extends Comparable — here num — so each of those keys is a heap-allocated boxed double compared through a virtual compareTo.

FxDart's sortBy extracts first. One walk of the list writes every key into a Float64List (it noticed they were all double). Then it merges the keys and the rows together, sequentially: each comparison is two machine doubles from a typed array, using a VM compare on this data (the amounts are ordinary finite positives, so there is no NaN or -0.0 to force the slower compareTo path). One extraction per row, no boxing, no dispatch, and no random index chase. An earlier sortBy did decorate by sorting a list of indices with List.sort; that is gone. The merge is stable by construction — equal keys keep their input order on both sides now.

The honest limit: when the keys are not uniformly double, int, or String, sortBy falls back to a generic comparator and the advantage disappears. Memory at a million rows is close (about 123 MB vs 130 MB) — both keep the row copy plus a scratch buffer. The amounts here are all distinct, so stability does not show on the printed three lines.

Benchmark

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

N = 100

Time Tie

Native Dart 20 µs
FxDart 5.6 µs

Peak memory Tie

Native Dart 16.6 MB
FxDart 16.5 MB

N = 10,000

Time FxDart wins

Native Dart 3.31 ms
FxDart 846 µs

Peak memory FxDart wins

Native Dart 23.2 MB
FxDart 21.4 MB

N = 1,000,000

Time FxDart wins

Native Dart 521.1 ms
FxDart 199.6 ms

Peak memory Native wins

Native Dart 123.2 MB
FxDart 129.7 MB

Bars are medians of repeated timed iterations in fresh processes per side (small N is batched for timer resolution). Sides within 5% of each other — or within 0.6 ms, a difference no person can perceive — count as a tie; close relative races are re-measured up to 5 runs. In an app, anything under a few milliseconds is invisible to the user regardless of which bar is shorter. Memory is peak process RSS. The Dart VM and the dataset are identical on both sides, so the difference between the two bars is what the pipeline itself holds onto.