소스 하나, 독립 리더 둘

우열 없음

요구사항

하나의 측정값 소스가 두 개의 독립적인 계산 — 합계와 최댓값 — 을 먹여 살리되, 정확히 한 번만 실행되어야 합니다. 소스는 실행될 때마다 카운터를 증가시킵니다; 합계, 최댓값, 그리고 한 번의 순회를 증명하는 카운터를 출력하세요. 데이터는 코드에 들어 있습니다; 두 버전 모두 예상 출력 아래에 표시된 줄들을 출력해야 합니다.

예상 출력
total: 74
peak: 25
source runs: 1

나란히 보기

RxDart

FxDart

차이가 나는 이유

여기서는 두 모델이 같은 벽에 부딪힙니다: 소스가 소비자마다 다시 시작된다는 것. 평범한 단일 구독 스트림을 두 번 listen하는 것은 오류이고; sync* 제너레이터를 두 번 순회하면 조용히 두 번 실행됩니다. 그리고 두 라이브러리는 같은 아이디어로 답합니다 — 한 번의 순회를 공유하기. RxDart는 스트림을 connectable로 만듭니다: publish()가 소스를 미루고, 두 리덕션이 구독한 뒤, connect()가 그들을 먹이는 단 하나의 구독을 시작합니다. FxDart의 tee는 대신 두 리덕션의 보폭을 맞춥니다: 다음 원소를 당기기 전에 각 원소가 합계와 최댓값을 모두 전진시키므로, 한 번의 순회가 아무것도 기억할 필요가 없습니다.

둘 다 버퍼를 피하며, 그 이유도 같습니다 — 모든 리더가 각 원소를 그것이 현재 원소인 동안 보기 때문입니다. 리더들을 먼저 붙게 만듦으로써 connect()가 사는 것이 그것이고, 리더들을 폴드로 — 독립적으로 전진할 수 있는 두 파이프라인이 아니라 시드와 스텝으로 — 받음으로써 tee가 사는 것도 그것입니다. 그 제약이 대가입니다. publish()는 원하는 어떤 스트림 연산자든 먹일 수 있지만, tee는 폴드만 먹입니다. 두 리더가 정말로 독립적인 파이프라인이라면 FxDart의 답은 fork입니다 — 같은 iterable 객체의 모든 fork는 버퍼링된 하나의 공유 순회 위의 커서이고 — 거기서는 버퍼가 돌아와, 가장 느린 커서가 소비할 때까지 모든 값을 붙들고 있습니다. 그래서 능력으로는 무승부입니다: 범용 도구는 양쪽 모두 메모리를 쓰고, 특화된 도구는 양쪽 모두 공짜입니다. 나머지 코드가 이미 살고 있는 모델에 맞는 쪽을 고르세요.

벤치마크

Apple M1 Max, RAM 32 GB · Dart 3.12.2 (AOT 컴파일) · 2026-08-18

N = 100

시간 무승부

RxDart 20 µs
FxDart 2.3 µs

최대 메모리 무승부

RxDart 16.7 MB
FxDart 16.3 MB

N = 1,000,000

시간 FxDart 승

RxDart 139.4 ms
FxDart 17.3 ms

최대 메모리 FxDart 승

RxDart 24.3 MB
FxDart 21.5 MB

막대는 사이드별로 새 프로세스에서 반복 측정한 중앙값입니다(작은 N은 타이머 해상도를 위해 배치 처리). 두 사이드가 서로 5% 이내이거나 — 사람이 지각할 수 없는 차이인 0.6ms 이내이면 — 무승부로 칩니다. 상대 차이가 근소한 경우는 최대 5회까지 다시 측정합니다. 앱에서는 어느 막대가 짧든 몇 밀리초 이하의 차이는 사용자에게 보이지 않습니다. 메모리는 프로세스 최대 RSS입니다. Dart VM과 데이터셋은 양쪽이 동일하므로, 두 막대의 차이가 곧 파이프라인 자체가 붙들고 있는 양입니다.