소스 하나, 독립 리더 둘
요구사항
하나의 측정값 소스가 두 개의 독립적인 계산 — 합계와 최댓값 — 을 먹여 살리되, 정확히 한 번만 실행되어야 합니다. 소스는 실행될 때마다 카운터를 증가시킵니다; 합계, 최댓값, 그리고 한 번의 순회를 증명하는 카운터를 출력하세요. 데이터는 코드에 들어 있습니다; 두 버전 모두 예상 출력 아래에 표시된 줄들을 출력해야 합니다.
예상 출력
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는
버퍼링된 하나의 공유 순회 위의 커서이고 — 거기서는 버퍼가 돌아와,
가장 느린 커서가 소비할 때까지 모든 값을 붙들고 있습니다. 그래서
능력으로는 무승부입니다: 범용 도구는 양쪽 모두 메모리를 쓰고,
특화된 도구는 양쪽 모두 공짜입니다. 나머지 코드가 이미 살고 있는
모델에 맞는 쪽을 고르세요.
벤치마크
N = 100
시간 무승부
최대 메모리 무승부
N = 1,000,000
시간 FxDart 승
최대 메모리 FxDart 승
막대는 사이드별로 새 프로세스에서 반복 측정한 중앙값입니다(작은 N은 타이머 해상도를 위해 배치 처리). 두 사이드가 서로 5% 이내이거나 — 사람이 지각할 수 없는 차이인 0.6ms 이내이면 — 무승부로 칩니다. 상대 차이가 근소한 경우는 최대 5회까지 다시 측정합니다. 앱에서는 어느 막대가 짧든 몇 밀리초 이하의 차이는 사용자에게 보이지 않습니다. 메모리는 프로세스 최대 RSS입니다. Dart VM과 데이터셋은 양쪽이 동일하므로, 두 막대의 차이가 곧 파이프라인 자체가 붙들고 있는 양입니다.