CRDT benchmarks
Reproducible, statistically reported benchmarks of 5 JavaScript CRDT libraries, with an analysis of why each library performs as it does. Every number below is the median of 10 independent process runs; timing cells show the half-width of the bootstrapped 95% confidence interval of the median.
These benchmarks indicate the absence of performance pitfalls; they do not rank libraries. Projects make different trade-offs, and a library that loses one row may win another by a wide margin.
Analysis
How to read the numbers
The benchmark models one specific usage pattern: every operation is its own transaction, and every update is sent to the peer and applied immediately. That is what a collaborative text editor does with keystrokes. It rewards libraries with cheap transactions and cheap per-update application, and it penalises libraries that assume changes will be batched. Read the results with that pattern in mind; a library that is slow here may be fast in an application that batches.
N = 6000 is small. Most B1 to B3 documents are a few kilobytes. B4 (a 105,000 character document built by 260,000 edits) and C1 (200,000 edits per peer, or 30,000 peers) are the only tests at a realistic size, and they are where the libraries separate most.
The numbers are stable: the median coefficient of variation across the ten runs is about 5% for every library. Differences smaller than the confidence intervals of both cells are not differences. Sub-millisecond cells vary by 20% or more and should be read as "very fast", not compared.
json-joy
json-joy is the fastest library in 16 of the 23 timing rows. It replays the real-world trace in 119 ms (Yjs: 2.56 s), merges 1,540 concurrent map writes in 5.8 ms (Yjs: 47 ms), and completes both C1 tests in about two seconds, where the others take between 20 s and 13 minutes.
We checked whether the harness gives it an advantage. It does not. json-joy produces one binary patch per edit (259,778 patches, 4.9 MB, for B4), a peer that applies them one at a time ends with the correct text, and every concurrent test passed the harness's peer-equality assertions. The advantage holds at each layer we could separate on the B4 trace:
| B4 trace, no receiver | Yjs | json-joy |
|---|---|---|
| CRDT operations only, no update encoding | 874 ms | 91 ms |
| Plus encoding one update per edit | 1.34 s (V1) / 2.3 s (V2) | 108 ms |
| Plus a second peer applying every update | 2.2 s (V1) / 3.6 s (V2) | 186 ms |
Its core is about ten times faster than Yjs's, and its patch encoding costs almost nothing (17 ms for 260,000 patches) where Yjs's costs more than the edits themselves.
What it gives up. json-joy keeps no operation log. It cannot reconstruct past versions or show history, which Loro and Automerge can. Its snapshot of a map keeps only the current value of each key, which is why its B3 map documents are a few dozen bytes. Against Yjs this is a like-for-like comparison, since Yjs has no operation log either; against Loro and Automerge, part of the gap is that trade-off.
Its measured weaknesses: inserting at the front of a text or list is slow (6,000 prepends take 481 ms against 33 ms for Ywasm and 135 ms for Yjs), its documents for arrays of numbers are three times larger than Yjs's (108 kB against 36 kB), and its update messages for random-position inserts are larger (80 kB against 33 kB in B2.2). We did not investigate the cause of the prepend cost.
What we did not check: its algorithmic behaviour on documents much larger than B4, and its implementation. It converged correctly in every test, but we did not read or profile the code.
Automerge
Automerge 3.4.1 is the slowest library in every timing row but one. Four separate causes account for it. We measured each in isolation.
Applying a remote change costs time proportional to the document, inside the WebAssembly core. We split the receiver's loadIncremental into the wasm call and the JavaScript step that rebuilds the document object afterwards. The wasm call grows linearly with the text: 286 ms per 1,500 calls on a small document, 1,727 ms per 1,500 calls once the text reaches 4,500 characters. The JavaScript step stays at 18 ms. The same 6,000 changes applied in one call take 30 ms in total. A stream of small updates therefore pays a document-sized cost on every update, which makes B1, B2, B3 and C1.2 quadratic. For large maps the JavaScript step grows as well, because it materialises the whole map after each patch.
Editing at a random position inside one change also costs time proportional to the text. A single change call containing many splices runs at 63 µs per splice on a 33,000-character text and 365 µs per splice at 260,000 characters. C1.1 performs 200,000 splices in one change per peer with no receiver involved, and takes 451 s. Yjs performs the same splices in about a microsecond each.
Every change is a commit. With one edit per change and no receiver, Automerge costs a constant 30 µs per change: transaction setup, the proxy, hashing the change, and encoding it. Yjs costs 3 to 9 µs for the same transaction. B4 makes 260,000 changes, so this alone is 8 s of its 13.5 s.
Loading rebuilds the state from history. Automerge stores the operation log and reconstructs the document on load, so the B4 document takes 652 ms to open. Loro's snapshot stores the materialised state and opens in 4.5 ms. The same choice gives Automerge the smallest text documents: 129 kB for B4 against 227 kB for Yjs.
The first two are almost certainly new in the 3.x series. The 2024 run of these benchmarks, on slower hardware, had Automerge 2.1.10 completing B1.1 in 343 ms; 3.4.1 takes 4.7 s. Automerge 3 moved to a compressed columnar in-memory representation to reduce memory use. Both costs are consistent with inserting into a compressed structure, but we did not confirm that in the source.
In practice: automerge-repo's sync protocol batches incoming changes, which removes most of the first cost. The second and third remain, and they are what a text editor doing one change per keystroke will experience.
Yjs and Ywasm
Ywasm is the Rust implementation of the Yjs algorithm, used from JavaScript through WebAssembly bindings. The two produce byte-identical documents. Their speed differs by the direction of the cost.
Inside a single transaction, Ywasm is three to four times faster than Yjs: prepending 6,000 items takes 33 ms against 135 ms, and appending 6,000 numbers takes 34 ms against 141 ms.
When every operation is its own transaction, each one crosses the JavaScript–WebAssembly boundary, and Ywasm is much slower: 16.9 s for the B4 trace against 2.56 s, and 1.17 s against 96 ms for merging concurrent inserts and deletes (B2.4). Ywasm's C1.1 is quadratic. Doubling the number of operations multiplied its time by 4.4 in our scaling test, and the full test takes 773 s. Yjs completes the same test in 24 s.
Both take about 20 s for C1.2, applying 30,000 updates from 30,000 distinct clients, and 5 to 6 s to load the resulting document. Loro takes 1.9 s and 0.35 s. We did not investigate the cause; it is the only test where the number of distinct clients is large, so it is likely related to per-client state.
Two settings inherited from the upstream benchmark affect Yjs and Ywasm. Both run with gc: false, which retains deleted content. That costs nothing in time but makes the documents larger (227 kB for B4 instead of 160 kB with garbage collection on, and 7.8 MB in B3.3 where 1,540 clients each wrote a 6 kB string to the same key). Both also encode updates in Yjs's V2 format. The V2 encoder has a fixed cost per update that dominates on single-character edits: Yjs completes B4 in 1.34 s with the default V1 format and 2.3 s with V2. Most applications use V1. We kept V2 to stay comparable with the upstream results, so the Yjs timings for per-keystroke workloads are about 40% higher than a typical application would see.
Loro
Loro loads documents faster than any other library: 4.5 ms for the B4 document, 50 ms for the 6.75 MB C1.1 document (Yjs: 1.27 s), 351 ms for the C1.2 document (Yjs: 6.45 s). Its snapshot format stores both the operation log and the materialised state, so loading does not replay history. The same format makes its documents 1.5 to 3 times larger than Yjs's.
Its timings are the most even. Apart from one pattern it is never more than about 8 times slower than the fastest library in a row, and it is the fastest on B2.2 (concurrent random inserts) and second on both C1 tests. The exception is many clients concurrently inserting text into the same container (B3.4 and B3.5): 350 ms against 41 ms for Yjs.
Its per-edit update messages are about three times the size of Yjs's (88 bytes against 27 for a single character), and exporting an update after every commit is most of its B4 time: replaying the trace takes 0.25 s with no commits, 0.48 s with a commit per edit, and 2.4 s with a commit per edit and an update exported after each. An application that commits per keystroke but exports updates less often would see the lower figure.
One observation we could not explain. When B1, B2 and B3, which create about 8,000 short-lived documents, ran before B4 in the same Node process, Loro's B4 replay took 6.7 s instead of 2.4 s, consistently. Any two of the three suites first had no effect. The campaign therefore runs each suite in a fresh process for every library, and the reported number is the clean one. Applications that create many documents in one process may want to check for this.
Bundle size
The pure-JavaScript libraries ship in 24 kB (Yjs) and 49 kB (json-joy) gzipped. The WebAssembly libraries ship in 350 kB (Ywasm), 1.08 MB (Loro) and 1.14 MB (Automerge). Almost all of the difference is the wasm binary itself.
Choosing
- If you need version history, time travel, or branching, the choice is between Loro and Automerge; Yjs, Ywasm and json-joy do not keep an operation log. Of the two, Loro is faster in every row of this benchmark and loads documents 100 times faster.
- If you send each edit as it happens, json-joy, Yjs and Loro all handle 260,000 single-character edits in under three seconds. Automerge does not, and Ywasm's per-transaction cost makes it the wrong binding for that pattern; use Yjs instead.
- If you use Automerge, batch incoming changes before applying them, and expect large single changes to be slow.
- If bundle size matters, Yjs and json-joy are 20 to 50 times smaller than the WebAssembly libraries.
- json-joy is the fastest library here and produces competitive document sizes, but it is younger and less widely deployed than Yjs, and it has no operation log. Its slow prepend is the one workload to check.
What this benchmark does not measure
- Memory. The
memUsedmetric only sees the JavaScript heap, so it excludes the WebAssembly memory of Ywasm, Loro and Automerge. It is reported for continuity with upstream and should not be used to compare libraries. - Rich text formatting, tree and move operations, undo, or presence; only plain text, arrays and maps.
- Sync protocols and networking. Updates are handed from one document to the other in the same process.
- Browser performance. Everything runs in Node 24 on one machine.
- Documents larger than a few hundred kilobytes, apart from C1.
Figures
All results
Hover a cell for the full statistics and every individual run.
B1
| N = 6000 | yjs | ywasm | loro | automerge | json-joy |
|---|---|---|---|---|---|
| [B1.1] Append N characters (avgUpdateSize) | 27 B | 30 B | 88 B | 125 B | 25 B |
| [B1.1] Append N characters (docSize) | 6.0 kB | 6.0 kB | 12.4 kB | 3.8 kB | 6.1 kB |
| [B1.1] Append N characters (encodeTime) | 0.9 ms ±5% | 0.7 ms ±10% | 2.2 ms ±6% | 1.7 ms ±5% | 0.1 ms ±15% |
| [B1.1] Append N characters (memUsed) | ≈0 | ≈0 | 9.3 kB | ≈0 | 258.5 kB |
| [B1.1] Append N characters (parseTime) | 31 ms ±7% | 16 ms ±4% | 17 ms ±4% | 28 ms ±2% | 15 ms ±2% |
| [B1.1] Append N characters (time) | 141 ms ±3% | 141 ms ±4% | 160 ms ±4% | 4.73 s ±1% | 15 ms ±3% |
| [B1.2] Insert string of length N (avgUpdateSize) | 6.0 kB | 6.0 kB | 6.1 kB | 6.2 kB | 6.0 kB |
| [B1.2] Insert string of length N (docSize) | 6.0 kB | 6.0 kB | 12.3 kB | 3.8 kB | 6.1 kB |
| [B1.2] Insert string of length N (encodeTime) | 0.1 ms ±12% | 0.0 ms ±19% | 0.1 ms ±12% | 0.2 ms ±17% | 0.0 ms ±7% |
| [B1.2] Insert string of length N (memUsed) | ≈0 | ≈0 | ≈0 | ≈0 | 1.5 kB |
| [B1.2] Insert string of length N (parseTime) | 17 ms ±28% | 15 ms ±8% | 16 ms ±6% | 15 ms ±5% | 15 ms ±4% |
| [B1.2] Insert string of length N (time) | 0.4 ms ±15% | 0.2 ms ±13% | 0.4 ms ±5% | 10 ms ±5% | 0.2 ms ±13% |
| [B1.3] Prepend N characters (avgUpdateSize) | 27 B | 30 B | 87 B | 120 B | 32 B |
| [B1.3] Prepend N characters (docSize) | 6.0 kB | 6.0 kB | 15.4 kB | 3.8 kB | 29.9 kB |
| [B1.3] Prepend N characters (encodeTime) | 1.0 ms ±11% | 0.3 ms ±27% | 2.0 ms ±3% | 0.4 ms ±4% | 1.3 ms ±7% |
| [B1.3] Prepend N characters (memUsed) | 1.02 MB | 7.8 kB | 19.7 kB | 220.8 kB | 1.12 MB |
| [B1.3] Prepend N characters (parseTime) | 24 ms ±24% | 18 ms ±6% | 17 ms ±4% | 24 ms ±3% | 21 ms ±5% |
| [B1.3] Prepend N characters (time) | 135 ms ±4% | 33 ms ±7% | 135 ms ±4% | 4.63 s ±1% | 481 ms ±3% |
| [B1.4] Insert N characters at random positions (avgUpdateSize) | 29 B | 32 B | 88 B | 125 B | 25 B |
| [B1.4] Insert N characters at random positions (docSize) | 29.6 kB | 29.6 kB | 39.0 kB | 24.5 kB | 29.9 kB |
| [B1.4] Insert N characters at random positions (encodeTime) | 2.1 ms ±30% | 0.3 ms ±4% | 2.0 ms ±5% | 1.0 ms ±5% | 1.3 ms ±4% |
| [B1.4] Insert N characters at random positions (memUsed) | 1.16 MB | ≈0 | ≈0 | ≈0 | 1.07 MB |
| [B1.4] Insert N characters at random positions (parseTime) | 40 ms ±18% | 18 ms ±4% | 15 ms ±5% | 24 ms ±3% | 26 ms ±5% |
| [B1.4] Insert N characters at random positions (time) | 155 ms ±5% | 109 ms ±5% | 134 ms ±3% | 5.40 s ±0% | 76 ms ±4% |
| [B1.5] Insert N words at random positions (avgUpdateSize) | 36 B | 39 B | 95 B | 135 B | 31 B |
| [B1.5] Insert N words at random positions (docSize) | 87.9 kB | 87.9 kB | 135.7 kB | 94.3 kB | 86.6 kB |
| [B1.5] Insert N words at random positions (encodeTime) | 1.6 ms ±13% | 0.7 ms ±8% | 2.8 ms ±5% | 4.1 ms ±3% | 5.6 ms ±27% |
| [B1.5] Insert N words at random positions (memUsed) | 2.40 MB | 496 B | 976 B | 33.4 kB | 2.05 MB |
| [B1.5] Insert N words at random positions (parseTime) | 33 ms ±8% | 22 ms ±5% | 16 ms ±6% | 43 ms ±3% | 31 ms ±8% |
| [B1.5] Insert N words at random positions (time) | 158 ms ±3% | 262 ms ±4% | 136 ms ±3% | 30.35 s ±0% | 21 ms ±6% |
| [B1.6] Insert string, then delete it (avgUpdateSize) | 6.1 kB | 6.1 kB | 6.2 kB | 6.4 kB | 6.1 kB |
| [B1.6] Insert string, then delete it (docSize) | 6.0 kB | 6.0 kB | 6.4 kB | 3.8 kB | 53 B |
| [B1.6] Insert string, then delete it (encodeTime) | 0.1 ms ±30% | 0.0 ms ±5% | 0.1 ms ±8% | 0.2 ms ±11% | 0.0 ms ±15% |
| [B1.6] Insert string, then delete it (memUsed) | ≈0 | ≈0 | ≈0 | ≈0 | ≈0 |
| [B1.6] Insert string, then delete it (parseTime) | 16 ms ±18% | 16 ms ±4% | 14 ms ±5% | 15 ms ±3% | 16 ms ±5% |
| [B1.6] Insert string, then delete it (time) | 0.9 ms ±39% | 0.6 ms ±3% | 1.4 ms ±5% | 64 ms ±2% | 0.4 ms ±10% |
| [B1.7] Insert/Delete strings at random positions (avgUpdateSize) | 31 B | 34 B | 100 B | 139 B | 31 B |
| [B1.7] Insert/Delete strings at random positions (docSize) | 41.9 kB | 41.9 kB | 81.7 kB | 58.4 kB | 25.7 kB |
| [B1.7] Insert/Delete strings at random positions (encodeTime) | 2.2 ms ±22% | 0.5 ms ±9% | 3.0 ms ±5% | 2.2 ms ±6% | 1.6 ms ±15% |
| [B1.7] Insert/Delete strings at random positions (memUsed) | 1.29 MB | 832 B | 144 B | ≈0 | 920.8 kB |
| [B1.7] Insert/Delete strings at random positions (parseTime) | 34 ms ±24% | 19 ms ±5% | 16 ms ±8% | 39 ms ±2% | 22 ms ±9% |
| [B1.7] Insert/Delete strings at random positions (time) | 160 ms ±4% | 112 ms ±5% | 153 ms ±4% | 14.26 s ±1% | 24 ms ±7% |
| [B1.8] Append N numbers (avgUpdateSize) | 32 B | 35 B | 94 B | 129 B | 41 B |
| [B1.8] Append N numbers (docSize) | 35.6 kB | 35.6 kB | 71.6 kB | 27.0 kB | 107.7 kB |
| [B1.8] Append N numbers (encodeTime) | 0.2 ms ±150% | 0.2 ms ±7% | 1.5 ms ±7% | 1.1 ms ±7% | 3.9 ms ±8% |
| [B1.8] Append N numbers (memUsed) | ≈0 | 48.0 kB | ≈0 | 7.5 kB | 4.37 MB |
| [B1.8] Append N numbers (parseTime) | 20 ms ±17% | 24 ms ±6% | 18 ms ±6% | 28 ms ±3% | 55 ms ±3% |
| [B1.8] Append N numbers (time) | 141 ms ±4% | 38 ms ±9% | 155 ms ±5% | 4.08 s ±0% | 34 ms ±5% |
| [B1.9] Insert Array of N numbers (avgUpdateSize) | 35.7 kB | 35.7 kB | 35.7 kB | 31.2 kB | 93.8 kB |
| [B1.9] Insert Array of N numbers (docSize) | 35.7 kB | 35.7 kB | 71.6 kB | 27.0 kB | 83.1 kB |
| [B1.9] Insert Array of N numbers (encodeTime) | 0.4 ms ±23% | 0.1 ms ±7% | 1.2 ms ±6% | 0.9 ms ±15% | 2.5 ms ±5% |
| [B1.9] Insert Array of N numbers (memUsed) | ≈0 | ≈0 | 2.7 kB | 61.1 kB | 2.38 MB |
| [B1.9] Insert Array of N numbers (parseTime) | 20 ms ±13% | 18 ms ±5% | 16 ms ±9% | 18 ms ±5% | 33 ms ±17% |
| [B1.9] Insert Array of N numbers (time) | 5.8 ms ±22% | 2.6 ms ±4% | 10 ms ±3% | 22 ms ±6% | 15 ms ±4% |
| [B1.10] Prepend N numbers (avgUpdateSize) | 32 B | 35 B | 93 B | 124 B | 47 B |
| [B1.10] Prepend N numbers (docSize) | 35.7 kB | 35.7 kB | 76.8 kB | 27.0 kB | 107.7 kB |
| [B1.10] Prepend N numbers (encodeTime) | 1.6 ms ±2% | 0.3 ms ±6% | 2.6 ms ±6% | 1.1 ms ±4% | 3.3 ms ±14% |
| [B1.10] Prepend N numbers (memUsed) | 2.30 MB | 464 B | 1.2 kB | 49.1 kB | 4.48 MB |
| [B1.10] Prepend N numbers (parseTime) | 40 ms ±6% | 22 ms ±8% | 19 ms ±6% | 25 ms ±4% | 35 ms ±5% |
| [B1.10] Prepend N numbers (time) | 126 ms ±5% | 38 ms ±6% | 218 ms ±4% | 4.06 s ±1% | 514 ms ±5% |
| [B1.11] Insert N numbers at random positions (avgUpdateSize) | 34 B | 37 B | 94 B | 129 B | 41 B |
| [B1.11] Insert N numbers at random positions (docSize) | 59.1 kB | 59.1 kB | 100.6 kB | 47.6 kB | 107.7 kB |
| [B1.11] Insert N numbers at random positions (encodeTime) | 0.9 ms ±7% | 0.4 ms ±15% | 2.6 ms ±4% | 1.6 ms ±7% | 4.8 ms ±8% |
| [B1.11] Insert N numbers at random positions (memUsed) | 2.04 MB | ≈0 | 696 B | 19.3 kB | 4.43 MB |
| [B1.11] Insert N numbers at random positions (parseTime) | 30 ms ±9% | 25 ms ±3% | 19 ms ±4% | 28 ms ±3% | 43 ms ±2% |
| [B1.11] Insert N numbers at random positions (time) | 138 ms ±2% | 108 ms ±2% | 224 ms ±2% | 4.70 s ±1% | 163 ms ±8% |
B2
| N = 6000 | yjs | ywasm | loro | automerge | json-joy |
|---|---|---|---|---|---|
| [B2.1] Concurrently insert string of length N at index 0 (docSize) | 12.2 kB | 12.2 kB | 24.7 kB | 7.6 kB | 12.2 kB |
| [B2.1] Concurrently insert string of length N at index 0 (encodeTime) | 0.2 ms ±8% | 0.7 ms ±6% | 2.1 ms ±11% | 1.5 ms ±7% | 0.1 ms ±10% |
| [B2.1] Concurrently insert string of length N at index 0 (memUsed) | 34.7 kB | ≈0 | 13.2 kB | 14.2 kB | 283.9 kB |
| [B2.1] Concurrently insert string of length N at index 0 (parseTime) | 16 ms ±3% | 16 ms ±10% | 19 ms ±14% | 23 ms ±4% | 15 ms ±7% |
| [B2.1] Concurrently insert string of length N at index 0 (time) | 1.0 ms ±6% | 1.1 ms ±9% | 5.1 ms ±13% | 47 ms ±1% | 0.4 ms ±5% |
| [B2.1] Concurrently insert string of length N at index 0 (updateSize) | 6.1 kB | 6.1 kB | 6.2 kB | 9.5 kB | 6.1 kB |
| [B2.2] Concurrently insert N characters at random positions (docSize) | 66.5 kB | 70.2 kB | 79.8 kB | 50.2 kB | 60.4 kB |
| [B2.2] Concurrently insert N characters at random positions (encodeTime) | 1.2 ms ±15% | 0.6 ms ±5% | 3.6 ms ±3% | 1.8 ms ±5% | 1.9 ms ±12% |
| [B2.2] Concurrently insert N characters at random positions (memUsed) | 2.43 MB | ≈0 | ≈0 | 8.0 kB | 3.17 MB |
| [B2.2] Concurrently insert N characters at random positions (parseTime) | 47 ms ±2% | 18 ms ±5% | 14 ms ±5% | 22 ms ±4% | 45 ms ±9% |
| [B2.2] Concurrently insert N characters at random positions (time) | 37 ms ±3% | 165 ms ±4% | 55 ms ±4% | 318 ms ±2% | 73 ms ±6% |
| [B2.2] Concurrently insert N characters at random positions (updateSize) | 33.3 kB | 35.1 kB | 23.8 kB | 27.5 kB | 80.4 kB |
| [B2.3] Concurrently insert N words at random positions (docSize) | 179.3 kB | 181.4 kB | 269.2 kB | 181.4 kB | 175.7 kB |
| [B2.3] Concurrently insert N words at random positions (encodeTime) | 2.5 ms ±3% | 1.3 ms ±3% | 5.4 ms ±7% | 7.6 ms ±3% | 3.8 ms ±6% |
| [B2.3] Concurrently insert N words at random positions (memUsed) | 5.41 MB | ≈0 | 27.3 kB | 71.9 kB | 4.05 MB |
| [B2.3] Concurrently insert N words at random positions (parseTime) | 58 ms ±2% | 24 ms ±3% | 15 ms ±5% | 53 ms ±4% | 43 ms ±4% |
| [B2.3] Concurrently insert N words at random positions (time) | 69 ms ±4% | 438 ms ±3% | 54 ms ±8% | 617 ms ±3% | 32 ms ±8% |
| [B2.3] Concurrently insert N words at random positions (updateSize) | 89.5 kB | 90.6 kB | 62.9 kB | 122.9 kB | 114.5 kB |
| [B2.4] Concurrently insert & delete (docSize) | 281.0 kB | 282.0 kB | 391.5 kB | 289.1 kB | 233.7 kB |
| [B2.4] Concurrently insert & delete (encodeTime) | 4.6 ms ±7% | 2.1 ms ±5% | 9.4 ms ±5% | 13 ms ±6% | 4.3 ms ±14% |
| [B2.4] Concurrently insert & delete (memUsed) | 8.24 MB | 624 B | 416 B | 78.3 kB | 5.97 MB |
| [B2.4] Concurrently insert & delete (parseTime) | 79 ms ±5% | 33 ms ±4% | 16 ms ±4% | 81 ms ±3% | 55 ms ±5% |
| [B2.4] Concurrently insert & delete (time) | 96 ms ±3% | 1.17 s ±3% | 108 ms ±7% | 2.02 s ±1% | 62 ms ±5% |
| [B2.4] Concurrently insert & delete (updateSize) | 140.4 kB | 140.9 kB | 124.1 kB | 300.4 kB | 219.9 kB |
B3
| N = 6000 | yjs | ywasm | loro | automerge | json-joy |
|---|---|---|---|---|---|
| [B3.1] 20√N clients concurrently set number in Map (docSize) | 36.8 kB | 46.0 kB | 78.8 kB | 86.2 kB | 62 B |
| [B3.1] 20√N clients concurrently set number in Map (encodeTime) | 1.8 ms ±6% | 1.3 ms ±10% | 13 ms ±4% | 2.4 ms ±7% | 0.1 ms ±10% |
| [B3.1] 20√N clients concurrently set number in Map (memUsed) | 766.9 kB | ≈0 | 10.5 kB | 9.3 kB | ≈0 |
| [B3.1] 20√N clients concurrently set number in Map (parseTime) | 41 ms ±3% | 24 ms ±6% | 29 ms ±3% | 24 ms ±3% | 25 ms ±9% |
| [B3.1] 20√N clients concurrently set number in Map (time) | 47 ms ±4% | 49 ms ±4% | 28 ms ±3% | 1.00 s ±1% | 5.8 ms ±5% |
| [B3.1] 20√N clients concurrently set number in Map (updateSize) | 49.2 kB | 53.8 kB | 132.4 kB | 295.6 kB | 42.7 kB |
| [B3.2] 20√N clients concurrently set Object in Map (docSize) | 72.7 kB | 81.9 kB | 84.8 kB | 109.7 kB | 88 B |
| [B3.2] 20√N clients concurrently set Object in Map (encodeTime) | 2.7 ms ±4% | 0.7 ms ±12% | 11 ms ±3% | 3.4 ms ±4% | 0.1 ms ±23% |
| [B3.2] 20√N clients concurrently set Object in Map (memUsed) | 1.04 MB | ≈0 | ≈0 | ≈0 | ≈0 |
| [B3.2] 20√N clients concurrently set Object in Map (parseTime) | 42 ms ±4% | 21 ms ±5% | 27 ms ±5% | 33 ms ±2% | 23 ms ±5% |
| [B3.2] 20√N clients concurrently set Object in Map (time) | 51 ms ±3% | 49 ms ±5% | 28 ms ±3% | 1.86 s ±0% | 8.3 ms ±7% |
| [B3.2] 20√N clients concurrently set Object in Map (updateSize) | 85.1 kB | 89.7 kB | 171.4 kB | 387.4 kB | 89.7 kB |
| [B3.3] 20√N clients concurrently set String in Map (docSize) | 7.81 MB | 7.82 MB | 241.6 kB | 124.5 kB | 4.7 kB |
| [B3.3] 20√N clients concurrently set String in Map (encodeTime) | 34 ms ±6% | 2.6 ms ±8% | 34 ms ±4% | 43 ms ±3% | 0.1 ms ±9% |
| [B3.3] 20√N clients concurrently set String in Map (memUsed) | 8.79 MB | ≈0 | ≈0 | 6.8 kB | 6.4 kB |
| [B3.3] 20√N clients concurrently set String in Map (parseTime) | 81 ms ±3% | 29 ms ±22% | 100 ms ±5% | 59 ms ±3% | 22 ms ±7% |
| [B3.3] 20√N clients concurrently set String in Map (time) | 93 ms ±3% | 55 ms ±12% | 45 ms ±4% | 2.14 s ±1% | 3.8 ms ±8% |
| [B3.3] 20√N clients concurrently set String in Map (updateSize) | 7.83 MB | 7.83 MB | 7.91 MB | 8.08 MB | 7.82 MB |
| [B3.4] 20√N clients concurrently insert text in Array (docSize) | 26.6 kB | 31.2 kB | 101.0 kB | 96.3 kB | 37.1 kB |
| [B3.4] 20√N clients concurrently insert text in Array (encodeTime) | 1.8 ms ±7% | 0.2 ms ±27% | 9.3 ms ±6% | 2.1 ms ±5% | 2.0 ms ±7% |
| [B3.4] 20√N clients concurrently insert text in Array (memUsed) | 900.9 kB | ≈0 | 1.9 kB | 45.0 kB | 1.33 MB |
| [B3.4] 20√N clients concurrently insert text in Array (parseTime) | 30 ms ±2% | 20 ms ±7% | 41 ms ±9% | 31 ms ±2% | 28 ms ±7% |
| [B3.4] 20√N clients concurrently insert text in Array (time) | 43 ms ±4% | 45 ms ±7% | 353 ms ±1% | 2.08 s ±1% | 11 ms ±12% |
| [B3.4] 20√N clients concurrently insert text in Array (updateSize) | 52.7 kB | 57.4 kB | 137.5 kB | 324.7 kB | 58.9 kB |
| [B3.5] 20√N clients concurrently insert text (docSize) | 24.3 kB | 28.9 kB | 102.9 kB | 90.7 kB | 24.9 kB |
| [B3.5] 20√N clients concurrently insert text (encodeTime) | 1.4 ms ±11% | 0.3 ms ±9% | 8.6 ms ±6% | 2.3 ms ±6% | 1.0 ms ±16% |
| [B3.5] 20√N clients concurrently insert text (memUsed) | 546.9 kB | 488 B | 2.2 kB | 7.1 kB | 421.7 kB |
| [B3.5] 20√N clients concurrently insert text (parseTime) | 26 ms ±4% | 17 ms ±2% | 40 ms ±11% | 44 ms ±6% | 22 ms ±10% |
| [B3.5] 20√N clients concurrently insert text (time) | 41 ms ±4% | 43 ms ±4% | 350 ms ±2% | 2.59 s ±1% | 18 ms ±22% |
| [B3.5] 20√N clients concurrently insert text (updateSize) | 48.1 kB | 52.7 kB | 132.9 kB | 310.3 kB | 51.2 kB |
B4
| N = 6000 | yjs | ywasm | loro | automerge | json-joy |
|---|---|---|---|---|---|
| [B4] Apply real-world editing dataset (docSize) | 227.0 kB | 227.0 kB | 230.6 kB | 129.2 kB | 171.2 kB |
| [B4] Apply real-world editing dataset (encodeTime) | 4.9 ms ±5% | 2.5 ms ±4% | 10 ms ±4% | 18 ms ±3% | 2.1 ms ±3% |
| [B4] Apply real-world editing dataset (memUsed) | 2.96 MB | ≈0 | 23.0 kB | ≈0 | 3.42 MB |
| [B4] Apply real-world editing dataset (parseTime) | 19 ms ±8% | 10 ms ±4% | 4.5 ms ±5% | 652 ms ±2% | 11 ms ±4% |
| [B4] Apply real-world editing dataset (time) | 2.56 s ±1% | 16.87 s ±1% | 2.43 s ±1% | 13.48 s ±0% | 119 ms ±1% |
C1
| N = 6000 | yjs | ywasm | loro | automerge | json-joy |
|---|---|---|---|---|---|
| [C1.1] Concurrently insert & delete 100K (docSize) | 4.91 MB | 4.91 MB | 6.75 MB | 5.34 MB | 3.96 MB |
| [C1.1] Concurrently insert & delete 100K (encodeTime) | 124 ms ±5% | 45 ms ±7% | 141 ms ±5% | 223 ms ±5% | 124 ms ±6% |
| [C1.1] Concurrently insert & delete 100K (memUsed) | 137.91 MB | ≈0 | 23.7 kB | 1.31 MB | 99.22 MB |
| [C1.1] Concurrently insert & delete 100K (parseTime) | 1.27 s ±6% | 633 ms ±4% | 50 ms ±3% | 1.25 s ±3% | 1.16 s ±2% |
| [C1.1] Concurrently insert & delete 100K (time) | 24.31 s ±6% | 773.38 s ±9% | 2.10 s ±1% | 451.34 s ±1% | 1.83 s ±3% |
| [C1.1] Concurrently insert & delete 100K (updateSize) | 4.91 MB | 4.91 MB | 4.27 MB | 10.33 MB | 7.54 MB |
| [C1.1] Concurrently insert & delete 100K (versionSize) | 222.6 kB | 222.6 kB | 28 B | 64 B | 88 B |
| [C1.2] Concurrently set Map 100K (docSize) | 739.0 kB | 918.9 kB | 1.54 MB | 1.74 MB | 62 B |
| [C1.2] Concurrently set Map 100K (encodeTime) | 59 ms ±5% | 23 ms ±4% | 185 ms ±3% | 37 ms ±23% | 0.1 ms ±10% |
| [C1.2] Concurrently set Map 100K (memUsed) | 19.03 MB | ≈0 | ≈0 | ≈0 | ≈0 |
| [C1.2] Concurrently set Map 100K (parseTime) | 6.45 s ±2% | 5.13 s ±2% | 351 ms ±4% | 1.69 s ±2% | 110 ms ±8% |
| [C1.2] Concurrently set Map 100K (time) | 19.96 s ±1% | 18.91 s ±3% | 1.93 s ±1% | 447.11 s ±1% | 41 ms ±4% |
| [C1.2] Concurrently set Map 100K (updateSize) | 980.8 kB | 1.07 MB | 2.60 MB | 5.78 MB | 837.8 kB |
| [C1.2] Concurrently set Map 100K (versionSize) | 416.2 kB | 596.2 kB | 314.9 kB | 1.95 MB | 776.3 kB |