Commit 38caceb
authored
feat(hg_analytics): the scale thesis, measured — parallel + distributed + out-of-core (#26)
* feat(hg_analytics): rayon-parallel PageRank + betweenness (the scale thesis, measured)
Cashes "Rust + our architecture outscales them" with numbers, not assertion:
- pagerank_parallel: pull-based (each node's rank computed from its in-neighbours → no write
contention), O(E) work parallel, O(n) reductions serial. Memory-bandwidth-bound (random rank[]
gather) so it scales modestly — HONEST ceiling.
- betweenness_parallel: the source loop is embarrassingly parallel + COMPUTE-bound → near-linear.
Deterministic: sources split into a FIXED chunk count (independent of thread count) and partials
summed in chunk order, so the result is bit-identical on any core count.
Measured (8-core M-series, examples/scale_bench.rs, release):
PageRank 2M nodes / 20M edges / 20 iters: 1.78x, 147M edges·iter/s, Δ vs serial 8e-22
Betweenness 6k nodes / 48k edges: 4.86x (61% of linear), matches serial (Δ 9e-11)
Both bit-deterministic 1-thread == 8-thread. 12 tests (incl. parallel==serial + determinism).
The betweenness number is the real proof: the compute-bound analytics scale with cores, in Rust,
deterministically. Next legs (spec'd, not built): out-of-core CSR storage (kill the RAM ceiling) +
distributed query over the federation (federation partition = shard — the move Neptune can't make).
* style: cargo fmt (rustfmt CI)
* feat(hg_analytics): distributed (BSP) PageRank over sharded partitions — the federation wedge
Leg 2 of the scale thesis, and the one the centralized incumbents structurally can't copy: the
graph is range-partitioned into shards (a sovereign Autobase log = one Shard), each superstep every
shard computes its OWNED nodes locally in parallel from a globally-exchanged rank halo, then the
disjoint owned ranges are gathered. Only the O(n) halo crosses shard boundaries — the O(E) edges
stay sovereign per participant. That's Pregel/BSP, but native to our federation.
- Shard { lo, hi, in_adj } + partition_edges() + distributed_pagerank()
- Deterministic (disjoint owned ranges, fixed source order); matches single-graph PageRank EXACTLY.
Measured (8-core, 2M nodes / 20M edges / 20 iters, examples/scale_bench.rs):
distributed 8 shards: 1.35s == single-graph (max|Δ| ~0) 16 MB halo/superstep vs 20M edges local
(vs 2.77s monolithic parallel — partition locality also helps single-node)
Neptune can't do this: their data is central. Ours is partitioned by construction, so the query
pushes down and only ranks are exchanged. 13 tests (incl. sharded==single-graph at k∈{1,2,3,5}).
* feat(hg_analytics): out-of-core mmap CSR PageRank — kill the RAM ceiling
Leg 3 (the #1 real gap vs centralized in-memory engines): the O(E) edge structure lives in a
memory-mapped CSR file (paged by the OS), so only the O(n) rank vectors are heap-resident. PageRank
runs DIRECTLY over the mapping — edges are read from disk-backed pages, never materialized into a
heap Vec — so a graph LARGER than RAM is processable.
- ooc.rs: write_csr (in-edge CSR: offsets u64 / in_neighbors u32 / out_deg u32, aligned) + MmapCsr
(memmap2, zero-copy bytemuck views) + pagerank_mmap (rayon over the mmap, deterministic).
Measured (2M nodes / 20M edges / 20 iters):
CSR on disk: 104 MB (mmap'd) heap resident: ~32 MB (only O(n) rank vectors)
pagerank_mmap: 1.086s — FASTER than the 2.77s in-heap parallel (CSR is cache-friendly vs Vec<Vec>)
== in-memory PageRank exactly.
14 tests (incl. out_of_core == in_memory). This is single-machine; the file is the "disk", the OS
page cache is the buffer pool. The last mile is a bounded-RAM streaming build (the writer still uses
O(n+m) temp heap) — but the QUERY path is now RAM-ceiling-free.
* feat(hg_analytics): streaming O(n)-heap CSR builder — ingest graphs larger than RAM
Completes the out-of-core leg's INGEST side. write_csr_streaming builds the mmap CSR from a
re-iterable edge STREAM holding only O(n) heap (in_deg/offsets/out_deg/cursor) — it never
materializes the O(E) edges. Two streaming passes: (1) count degrees → offsets; (2) random-write
in-neighbours directly into the disk-backed (MmapMut) neighbours region. So you can now BUILD a
>RAM graph, not just query one.
Verified byte-identical to the batch write_csr, and pagerank_mmap over the streamed CSR == in-memory.
15 tests. Both ends of the RAM ceiling — build and query — are now bounded to O(n) heap.
* bench(ooc): 100M-edge out-of-core PageRank — RAM-ceiling break at scale
examples/ooc_scale.rs streams 100M edges from a generator straight into the mmap CSR (never
materializes the edge list) and runs PageRank over the disk-backed mapping.
Measured (10M nodes / 100M edges, 8-core):
in-heap edge Vec would be ~1600 MB; instead heap resident ~240 MB (O(n) only)
CSR on disk: 520 MB stream-build 2.72s pagerank_mmap(10) 5.80s 172M edges·iter/s
Σrank = 1.000000 (mass conserved) — correct at scale
* feat(ooc): bucketed external-memory CSR builder — fully-sequential-I/O ingest of >RAM graphs
write_csr_bucketed removes the random-write page-thrash of write_csr_streaming: destination nodes
are split into contiguous buckets and the neighbours region is emitted one bucket at a time in
order, so ALL output I/O is sequential. Peak heap is O(n) + O(m/num_buckets) — the per-bucket
buffer — independent of total edge count, so a truly larger-than-RAM neighbours array builds
cleanly (stream the edges once per bucket). Byte-identical to the batch write_csr at every bucket
count (b∈{1,2,3,5,8}). 16 tests.
* feat(dist): REAL multi-process distributed PageRank over TCP sockets
examples/dist_socket.rs — the distributed model over an ACTUAL network transport, not shared memory.
The coordinator partitions the graph, writes one shard file per participant, and spawns N worker
PROCESSES. Each BSP superstep it broadcasts the rank halo over TCP; each worker computes its owned
nodes from its LOCAL shard file (edges never leave the process) and returns its slice; the
coordinator gathers. Only the O(n) halo crosses the wire.
Measured (200k nodes / 2M edges / 4 worker processes, 20 supersteps):
68 ms over TCP 160 MB halo over the wire edges NEVER left their worker
== single-graph PageRank: max|Δ| 8.47e-21 (EXACT)
This closes the "single-machine simulation" asterisk on leg 2: distinct processes, real sockets,
real serialization, sovereign per-process edges — and the answer is exact. The Hypercore/Autobase
layer is the production transport; this proves the compute+exchange model over a real socket.
* feat(cc): distributed connected components — the partition-native model generalizes
connected_components (single-graph min-label propagation) + distributed_connected_components (BSP
over CcShards): each superstep every shard recomputes its owned labels in parallel from the
exchanged O(n) label halo; only labels cross shard boundaries, edges stay sovereign. Deterministic;
matches single-graph at any shard count (k∈{1,2,3,6}).
Proves the distributed wedge is NOT PageRank-specific — the same partition-native, only-the-halo-
crosses model works for the whole vertex-centric class. 17 tests.
* bench(warm): incremental warm-start PageRank — honest number
examples/warm_scale.rs: after a +50-edge delta on a 20M-edge graph, recompute from the prior fixed
point vs cold. Result is EXACT (max|Δ| 1.3e-15, same fixed point) but the speedup is a modest 1.3x —
reported straight, NOT dressed up. A well-mixed random graph converges fast even cold, so warm-start
helps only ~half the iterations here. The real incremental win is on slow-converging / large-diameter
graphs and tighter tolerances; this benchmark is the honest floor, not the ceiling.
* bench(ooc): billion-scale runner — honest single-box ceiling is ~500M edges
examples/billions.rs uses the bucketed (sequential-I/O) builder so it never thrashes. Measured on
a laptop: 50M nodes / 500M edges built + queried out-of-core with 1.2 GB heap (vs ~8 GB in-heap),
2.60 GB CSR on disk, 126M edges·iter/s, Σrank=1.0 (correct). Streaming (random-write) thrashes past
~100M edges once neighbours exceed page cache; bucketed completes (slow build, no thrash). 1B on
this box times out — the ceiling is the machine's RAM/disk, not the code. Cluster is the next
instrument; this proves the single-node out-of-core path to a half-billion edges.
* hg_analytics: Graph500 Kronecker (RMAT) generator for scale benchmarks
Deterministic seeded splitmix64 RMAT stream (A=0.57/B=0.19/C=0.19/D=0.05),
re-iterable for the two-pass CSR builders. scale -> 2^scale vertices,
edgefactor edges/vertex. The standard, no-download dataset for the weekend
cluster run.
* hg_analytics: boundary-only halo (ghost vertices) distributed PageRank
The scaling unlock. Plain distributed_pagerank broadcasts the full O(n) rank
vector to every shard each superstep (k*n). Boundary-only halo exchanges only
the ranks of the remote vertices a shard's edges reference (its ghosts), so the
recurring per-superstep cost is Sum|ghosts| << k*n. Bit-identical to serial
pagerank (max|delta| < 1e-12), deterministic (sorted ghost order), and
total_halo_bytes measures the real recurring network cost.
* hg_analytics: halo_bench example — measures boundary halo vs full broadcast
RMAT scale-16 (worst case, range partition): halo = 17% of full broadcast
(5.9x less), max|delta| vs serial 8e-17. Ring 1M (perfect locality): 1
ghost/shard, ~1e6x less, exact. This is the honest floor the edge-cut
partitioner improves on.
* hg_analytics: streaming edge-cut partitioner (Fennel + LDG)
Deterministic streaming partitioners that place each vertex on the shard where
its neighbours already live, minimizing edge cut and thus the boundary halo.
partition_edges_boundary_at + relabel_contiguous let a smart partition drive the
same boundary-halo PageRank on a non-uniform layout, bit-identical to serial.
Measured on RMAT scale-16, k=16 (halo_bench):
range : 85% cut, halo 1.43 MB (5.9x < full), balanced 1.0x
fennel: 20% cut, halo 640 KB (13.1x < full), 3.0x imbalance
ldg : 76% cut, halo 1.16 MB (7.2x < full), 1.1x balanced
Fennel = 4x fewer crossing edges, 2.2x less recurring network vs range.
* hg_analytics: dress_rehearsal — end-to-end scaling curve + cluster sizing
Graph500 -> Fennel -> boundary-halo PageRank across scale 14..20, reporting the
numbers that size the cluster before the spend: fattest-shard RAM, per-node halo
bytes/superstep, total network/superstep, edge cut %, throughput.
Measured (k=16, RMAT ef=16):
scale 20 (1M nodes / 16.8M edges): cut 15.1%, fattest shard 132 MB,
halo/node 3.1 MB, 0.76s/20 iters (442M e.it/s), Sigma-rank 1.000 exact.
Edge cut IMPROVES with scale (23.3% -> 15.1%).
Extrapolation: 125.8 bytes/edge -> 1B edges = 8 nodes @ 16GB / 4 @ 32GB;
10B = 20-79 nodes; 100B = 197-787 nodes. The Saturday plan, measured not guessed.
* hg_analytics: boundary-halo connected components
Mirrors the boundary-halo PageRank path for CC: only ghost LABELS (u32) cross
shard boundaries, not the full O(n) label vector. Bit-identical to single-graph
connected_components on a multi-component graph. Proves the boundary-halo model
is not PageRank-specific but covers the vertex-centric class (PageRank + CC).
* hg_analytics: dist_boundary — real multi-process boundary-halo runtime
The actual cluster artifact, proven locally over real TCP first. 8 worker
PROCESSES, Fennel-partitioned + relabelled graph. Workers KEEP their owned ranks
across supersteps; only the boundary crosses the wire both ways (ghost halos
down, boundary-owned values + dangling scalar up). NO O(n) message per step;
one final O(n) gather collects the answer.
Measured (scale-18, 4.2M edges, 8 procs, 25 iters): 122ms, |B|=49% of n,
wire 64.5 MB total vs full-broadcast BSP 419 MB (6.5x less), max|delta| 8.6e-16
vs single-graph PageRank (EXACT). Edges never leave their worker file.
* deploy/bench: containerize boundary-halo runtime + GKE manifests
One image is every pod (HG_ROLE=coordinator|worker). dist_boundary refactored
to ship shards over the socket (no shared filesystem) and take roles/graph size
from env (HG_ROLE/HG_COORD/HG_ORDINAL, HG_SCALE/HG_EDGEFACTOR/HG_ITERS/HG_SHARDS);
worker retries the coordinator connect so k8s pod-start order doesn't matter.
Local self-contained run unchanged (still 8.6e-16 exact).
deploy/bench: Dockerfile (Rust 1.96 multi-stage -> slim), k8s configmap +
headless coordinator Service/Job + Indexed worker Job (JOB_COMPLETION_INDEX =
ordinal), run.sh (build/push/apply/stream/teardown, keeps fan-out == HG_SHARDS),
README runbook. Default = scale 26 / 8 shards (~1B edges, the MVP proof).
Spin up -> work -> TEAR DOWN discipline; cluster create/delete stays explicit.
* hg_analytics: vs_baseline head-to-head against networkx + scipy
Same Graph500 graph, same machine, matched damping/tol. Rust emits the shared
graph + our result; the Python script runs BOTH pure-Python networkx AND scipy's
optimized sparse power iteration (no strawman) and reports speedup + top-100
agreement.
Measured (8-core laptop):
scale 15 (524K edges): rust 9.4ms | scipy 32ms (3.4x, 100% agree) | nx 271ms (29x)
scale 17 (2.1M edges): rust 44ms | scipy 122ms (2.8x, 100% agree) | nx 1.28s (29x)
~3x faster than BLAS-optimized sparse single-node, 100% same ranking — AND we
distribute (boundary-halo), which neither baseline can.
* hg_analytics: dist_p2p — pure peer-to-peer boundary halo (no coordinator relay)
Removes the coordinator from the hot path: workers form a mesh and exchange
ghost values DIRECTLY (worker c sends d exactly the owned values that are d's
ghosts). The coordinator keeps only setup + a per-step SCALAR dangling all-reduce
(k floats up, k down — O(k), a barrier) + the one-time final gather. Mesh is
built deterministically (connect-higher / accept-lower, announce id), reader
thread per peer drains into a channel to avoid TCP deadlock.
Measured (scale-18, 4.2M edges): k=8 → 99.99% of recurring bytes peer-to-peer,
coordinator only 128 B/step; k=16 mesh (120 conns) same. max|delta| 8.6e-16
EXACT, deterministic across runs. This is the unlock past ~tens of nodes:
coordinator traffic is O(k), independent of graph size.
Fixed en route: ghost-scatter wrote to local_rank[ghost_pos] instead of
[owned+ghost_pos], freezing the halo at the uniform init (had shown 3.9e-3);
now bit-exact.
* hg_analytics: distributed BFS (boundary-halo traversal)
distributed_bfs_boundary — hop distance from a source via Bellman-Ford-style
relaxation over the undirected boundary CC shards; only ghost distances (u32)
cross. This is the TRAVERSAL shape (frontier expansion, not fixpoint smoothing),
so the boundary-halo model now covers the LDBC traversal class too, alongside
PageRank + connected-components. Converges in O(diameter) supersteps, bit-exact
vs serial BFS. Weighted SSSP is the same loop with +w(u,v) instead of +1.
Distributed boundary-halo suite: PageRank, connected-components, BFS. 27 tests.
* hg_analytics: ldbc_suite — self-verifying distributed benchmark scorecard
Runs the whole boundary-halo suite (PageRank / WCC / BFS = 3 LDBC Graphalytics
kernels, 3 computational shapes) on ONE Fennel-partitioned Graph500 graph and
prints a scorecard: per-kernel time, recurring halo bytes, and bit-exact
verification vs single-graph. The Saturday deliverable in one command.
Measured scale-20 (1M nodes / 16.8M edges, 16 shards): partition 0.86s;
PR 1.04s (halo 7.6MB, max|Δ| 6e-17), WCC 0.24s, BFS 0.20s — ALL EXACT.
BFS from 0 reached 645803/1048576 in max 5 hops.
* deploy/bench: one-command Saturday runbook + Cloud Build (no local docker)
saturday.sh: full ephemeral lifecycle — GKE create (spot) -> Cloud Build image
-> run -> TEAR DOWN cluster on exit. --preflight validates auth/APIs/AR-repo/
manifests and prints the plan while spending nothing (caught the known gcloud
re-auth blocker cleanly in a dry-run).
run.sh: BUILDER=docker|cloudbuild (cloudbuild needs no local docker daemon).
cloudbuild.yaml: server-side build honouring the non-root Dockerfile.
Dockerfile now builds BOTH runtimes (dist_boundary relay = MVP default,
dist_p2p mesh = scale path; p2p needs POD_IP->HG_ADVERTISE wiring).
README updated with the one-command path + preflight.
* hg_analytics: BENCHMARKS.md — reproducible results doc (the publishable capstone)
Documents every measured number with the exact command to reproduce it: single-
node vs networkx/scipy (~3x faster than sparse-BLAS, 100% agreement), boundary-
halo scaling (Fennel 13x less wire), real multi-process TCP (relay 6.5x, P2P
99.99% peer-to-peer), the 3-kernel LDBC suite (all bit-exact), out-of-core 500M
edges, and the ~126 B/edge cluster sizing. Honest-limits section included.
Turns the scattered examples into one verifiable benchmark story.
* hg_analytics: weighted SSSP (4th LDBC kernel), boundary-halo distributed
distributed_sssp_boundary — weighted single-source shortest path via BSP
Bellman-Ford relaxation over BoundaryWShard (adjacency carries edge weights);
only ghost DISTANCES (f64) cross. Shortest-path distances are unique so the
min-fixpoint is order-independent and EXACT vs serial Dijkstra (max|Δ| 0). BFS
is the w≡1 special case.
Wired into ldbc_suite → now 4/6 LDBC Graphalytics kernels (BFS, PR, WCC, SSSP),
all bit-exact. Scale-20/16sh: SSSP 0.49s, halo 10.8MB, max|Δ| 0. BENCHMARKS.md
updated. 28 tests.
* hg_analytics: CDLP community detection (5th LDBC kernel), boundary-halo distributed
distributed_cdlp_boundary — LDBC community detection by SYNCHRONOUS label
propagation for a fixed iteration count: each round a vertex adopts the most
frequent neighbour label, ties -> smallest id. Only ghost labels (u32) cross.
The label-VOTING shape (distinct from WCC's min-propagation). Deterministic and
bit-exact vs single-graph CDLP.
Wired into ldbc_suite -> 5/6 LDBC Graphalytics kernels (BFS, PR, WCC, CDLP,
SSSP), all bit-exact. Scale-20/16sh: CDLP 3.90s (per-node label histogram x 10
rounds), rest sub-second. Only LCC (needs 2-hop halo) remains. 29 tests.
* hg_analytics: LCC (6th LDBC kernel) — full LDBC Graphalytics suite complete
distributed_lcc_boundary — local clustering coefficient with a 2-HOP boundary
halo: ghosts carry their adjacency (not a scalar) so each owned vertex can count
edges among its neighbours. Single-pass, so the heavier halo is exchanged once.
LCC(v) = Sum|N(a) cap N(v)| / (deg*(deg-1)). Bit-exact vs single-graph (max|D| 0).
Wired into ldbc_suite -> the FULL six-kernel LDBC Graphalytics suite (BFS, PR,
WCC, CDLP, LCC, SSSP), boundary-halo distributed, ALL verified bit-exact.
Scale-18/16sh: 5 kernels sub-second-to-~1s; LCC 21s (triangle counting on RMAT
power-law hubs is adversarial — inherent, single-pass, exact). 30 tests.
* hg_analytics: head-to-head vs a REAL graph database (KuzuDB)
vs_kuzu.py — the comparison that usually 'needs a cluster', run in-process:
KuzuDB (embedded graph DB, Cypher + native page_rank) on the SAME RMAT graph,
same machine, compute-to-compute.
Measured: scale-17 (2.1M edges) hg 46ms vs Kuzu 559ms = 12.2x faster;
scale-18 (4.2M edges) hg 109ms vs Kuzu 1122ms = 10.3x faster; top-100 ranking
100% identical both. Kuzu also pays a ~0.5s bulk-load we don't. Honest caveats:
Kuzu is single-machine embedded, default damping/iters may differ (but ranking
agrees 100%). Closes the biggest open gap — a real graph-DB head-to-head, not
just linear-algebra libraries. BENCHMARKS.md updated.
* hg_analytics: union-find CC + extend graph-DB head-to-head to WCC
The Kuzu head-to-head exposed a real gap: our single-machine WCC used label
propagation (chosen for the distributed BSP path) and LOST to Kuzu's union-find
(117ms vs 98ms). Added connected_components_uf (union-find, path halving + union
by size, near-linear) — induces the same partition as the label-prop reference,
verified by test. Cut our WCC to 13ms.
Result: hg_analytics now beats KuzuDB (a real embedded graph DB) on BOTH kernels
at scale-18/4.2M edges: PageRank 10.1x (ranking 100% agree), WCC 7.6x (same 88104
components). vs_baseline emits WCC timing; vs_kuzu compares both. 31 tests.1 parent f65cbf5 commit 38caceb
30 files changed
Lines changed: 5098 additions & 0 deletions
File tree
- crates/hg_analytics
- examples
- src
- deploy/bench
- k8s
- scripts/bench
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
0 commit comments