test_mpack_perf.py
python
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3
docs: docstring sprint contract→find-symbol — idiomatic run…
Sonnet 4.6
patch
145 days ago
| 1 | """MPack performance benchmarks — prove parity with GitHub-class operations. |
| 2 | |
| 3 | All tests use LocalFileTransport (loopback — zero network overhead) so the |
| 4 | measured time is pure protocol + I/O cost with no network latency. |
| 5 | |
| 6 | Targets |
| 7 | ------- |
| 8 | - 1 000-object push : < 3 s (GitHub does ~1 s over WAN for same size) |
| 9 | - 1 000-commit clone : < 3 s |
| 10 | - 500-object stream : < 1 s (writer + reader round-trip, no disk I/O) |
| 11 | - zstd > zlib speed : compress 500 objects faster with zstd (when available) |
| 12 | - Peak memory : bounded by 2 × largest single object during push |
| 13 | """ |
| 14 | from __future__ import annotations |
| 15 | |
| 16 | import datetime |
| 17 | import hashlib |
| 18 | import json |
| 19 | import pathlib |
| 20 | import time |
| 21 | |
| 22 | import pytest |
| 23 | |
| 24 | from muse.core.snapshot import compute_commit_id, compute_snapshot_id |
| 25 | from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot |
| 26 | from muse.core.object_store import write_object |
| 27 | from muse.core._types import long_id |
| 28 | |
| 29 | _DT = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) |
| 30 | |
| 31 | |
| 32 | def _sha(data: bytes) -> str: |
| 33 | return long_id(hashlib.sha256(data).hexdigest()) |
| 34 | |
| 35 | |
| 36 | def _init_repo(path: pathlib.Path) -> pathlib.Path: |
| 37 | muse = path / ".muse" |
| 38 | for d in ("commits", "snapshots", "objects", "refs/heads"): |
| 39 | (muse / d).mkdir(parents=True, exist_ok=True) |
| 40 | (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") |
| 41 | (muse / "repo.json").write_text( |
| 42 | json.dumps({"repo_id": "perf-test", "domain": "code"}), encoding="utf-8", |
| 43 | ) |
| 44 | return path |
| 45 | |
| 46 | |
| 47 | def _make_chain(root: pathlib.Path, n_commits: int, files_per_commit: int = 1) -> list[str]: |
| 48 | """Create a linear commit chain; return all commit IDs tip-first.""" |
| 49 | parent: str | None = None |
| 50 | commit_ids: list[str] = [] |
| 51 | for i in range(n_commits): |
| 52 | manifest: dict[str, str] = {} |
| 53 | for j in range(files_per_commit): |
| 54 | content = f"# commit {i} file {j}\n".encode() * 20 |
| 55 | oid = _sha(content) |
| 56 | write_object(root, oid, content) |
| 57 | manifest[f"f{i}_{j}.py"] = oid |
| 58 | snap_id = compute_snapshot_id(manifest) |
| 59 | write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest, created_at=_DT)) |
| 60 | parent_ids = [parent] if parent else [] |
| 61 | cid = compute_commit_id(parent_ids, snap_id, f"commit {i}", _DT.isoformat()) |
| 62 | write_commit(root, CommitRecord( |
| 63 | commit_id=cid, repo_id="perf-test", branch="main", |
| 64 | snapshot_id=snap_id, message=f"commit {i}", committed_at=_DT, |
| 65 | parent_commit_id=parent, |
| 66 | )) |
| 67 | commit_ids.append(cid) |
| 68 | parent = cid |
| 69 | ref = root / ".muse" / "refs" / "heads" / "main" |
| 70 | ref.write_text(commit_ids[-1], encoding="utf-8") |
| 71 | return commit_ids |
| 72 | |
| 73 | |
| 74 | # --------------------------------------------------------------------------- |
| 75 | # Stream writer/reader throughput |
| 76 | # --------------------------------------------------------------------------- |
| 77 | |
| 78 | |
| 79 | class TestStreamThroughput: |
| 80 | def test_500_object_stream_under_1s(self) -> None: |
| 81 | from muse.core.mpack import MPackStreamWriter, MPackStreamReader |
| 82 | w = MPackStreamWriter() |
| 83 | r = MPackStreamReader() |
| 84 | n = 500 |
| 85 | # Pre-build object data |
| 86 | objects = [f"content of object {i}\n".encode() * 10 for i in range(n)] |
| 87 | |
| 88 | t0 = time.monotonic() |
| 89 | body = w.write_header(op="push", branch="main", n_objects=n, n_commits=1) |
| 90 | for raw in objects: |
| 91 | body += w.write_object_raw( |
| 92 | object_id=_sha(raw), raw_bytes=raw, compress="zlib", |
| 93 | ) |
| 94 | body += w.write_commit_pack(commits=[], snapshots=[]) |
| 95 | body += w.write_end(n_objects=n, n_commits=1) |
| 96 | r.feed(body) |
| 97 | frames = list(r.frames()) |
| 98 | elapsed = time.monotonic() - t0 |
| 99 | |
| 100 | assert len([f for f in frames if f["t"] == "O"]) == n |
| 101 | assert elapsed < 1.0, f"500-object stream took {elapsed:.3f}s — target < 1s" |
| 102 | |
| 103 | def test_1000_object_stream_under_2s(self) -> None: |
| 104 | from muse.core.mpack import MPackStreamWriter, MPackStreamReader |
| 105 | w = MPackStreamWriter() |
| 106 | r = MPackStreamReader() |
| 107 | n = 1000 |
| 108 | objects = [f"object data {i}\n".encode() * 15 for i in range(n)] |
| 109 | |
| 110 | t0 = time.monotonic() |
| 111 | body = w.write_header(op="push", branch="main", n_objects=n, n_commits=1) |
| 112 | for raw in objects: |
| 113 | body += w.write_object_raw(object_id=_sha(raw), raw_bytes=raw) |
| 114 | body += w.write_commit_pack(commits=[], snapshots=[]) |
| 115 | body += w.write_end(n_objects=n, n_commits=1) |
| 116 | r.feed(body) |
| 117 | list(r.frames()) |
| 118 | elapsed = time.monotonic() - t0 |
| 119 | |
| 120 | assert elapsed < 2.0, f"1000-object stream took {elapsed:.3f}s — target < 2s" |
| 121 | |
| 122 | |
| 123 | # --------------------------------------------------------------------------- |
| 124 | # build_mpack throughput |
| 125 | # --------------------------------------------------------------------------- |
| 126 | |
| 127 | |
| 128 | class TestBuildMpackThroughput: |
| 129 | def test_100_commit_chain_build_under_2s(self, tmp_path: pathlib.Path) -> None: |
| 130 | from muse.core.pack import build_mpack |
| 131 | root = _init_repo(tmp_path) |
| 132 | commit_ids = _make_chain(root, n_commits=100, files_per_commit=5) |
| 133 | |
| 134 | t0 = time.monotonic() |
| 135 | bundle = build_mpack(root, [commit_ids[-1]]) |
| 136 | elapsed = time.monotonic() - t0 |
| 137 | |
| 138 | assert len(bundle["commits"]) == 100 |
| 139 | assert elapsed < 2.0, f"build_mpack(100 commits, 5 files each) took {elapsed:.3f}s" |
| 140 | |
| 141 | def test_build_and_apply_1000_objects_under_3s(self, tmp_path: pathlib.Path) -> None: |
| 142 | from muse.core.pack import build_mpack, apply_mpack |
| 143 | src = _init_repo(tmp_path / "src") |
| 144 | dst = _init_repo(tmp_path / "dst") |
| 145 | commit_ids = _make_chain(src, n_commits=50, files_per_commit=20) |
| 146 | |
| 147 | t0 = time.monotonic() |
| 148 | bundle = build_mpack(src, [commit_ids[-1]]) |
| 149 | apply_mpack(dst, bundle) |
| 150 | elapsed = time.monotonic() - t0 |
| 151 | |
| 152 | assert elapsed < 3.0, ( |
| 153 | f"build_mpack + apply_mpack (50 commits × 20 files) took {elapsed:.3f}s — target < 3s" |
| 154 | ) |
| 155 | |
| 156 | |
| 157 | # --------------------------------------------------------------------------- |
| 158 | # Compression speed comparison |
| 159 | # --------------------------------------------------------------------------- |
| 160 | |
| 161 | |
| 162 | class TestCompressionSpeed: |
| 163 | def test_zstd_not_slower_than_2x_zlib(self) -> None: |
| 164 | from muse.core.compression import ZSTD_AVAILABLE, compress_zstd, compress_zlib |
| 165 | if not ZSTD_AVAILABLE: |
| 166 | pytest.skip("zstd not installed") |
| 167 | data = b"source line\n" * 50_000 # ~600 KB |
| 168 | |
| 169 | t0 = time.monotonic() |
| 170 | for _ in range(10): |
| 171 | compress_zlib(data) |
| 172 | zlib_time = time.monotonic() - t0 |
| 173 | |
| 174 | t0 = time.monotonic() |
| 175 | for _ in range(10): |
| 176 | compress_zstd(data) |
| 177 | zstd_time = time.monotonic() - t0 |
| 178 | |
| 179 | assert zstd_time < zlib_time * 2.0, ( |
| 180 | f"zstd ({zstd_time:.3f}s) > 2× zlib ({zlib_time:.3f}s) — expected faster" |
| 181 | ) |
| 182 | |
| 183 | def test_zstd_ratio_on_source_code(self) -> None: |
| 184 | from muse.core.compression import ZSTD_AVAILABLE, compress_zstd, compress_zlib |
| 185 | if not ZSTD_AVAILABLE: |
| 186 | pytest.skip("zstd not installed") |
| 187 | data = b"def function(arg):\n return arg * 2\n" * 500 |
| 188 | zstd_ratio = len(compress_zstd(data)) / len(data) |
| 189 | zlib_ratio = len(compress_zlib(data)) / len(data) |
| 190 | # zstd should compress at least as well as zlib |
| 191 | assert zstd_ratio <= zlib_ratio * 1.15, ( |
| 192 | f"zstd ratio {zstd_ratio:.2%} vs zlib {zlib_ratio:.2%} — zstd too large" |
| 193 | ) |
| 194 | |
| 195 | |
| 196 | # --------------------------------------------------------------------------- |
| 197 | # apply_mpack throughput (clone simulation) |
| 198 | # --------------------------------------------------------------------------- |
| 199 | |
| 200 | |
| 201 | class TestApplyMpackThroughput: |
| 202 | def test_clone_simulation_500_commits_under_3s(self, tmp_path: pathlib.Path) -> None: |
| 203 | from muse.core.pack import build_mpack, apply_mpack |
| 204 | src = _init_repo(tmp_path / "src") |
| 205 | dst = _init_repo(tmp_path / "dst") |
| 206 | commit_ids = _make_chain(src, n_commits=500, files_per_commit=2) |
| 207 | |
| 208 | bundle = build_mpack(src, [commit_ids[-1]]) |
| 209 | |
| 210 | t0 = time.monotonic() |
| 211 | result = apply_mpack(dst, bundle) |
| 212 | elapsed = time.monotonic() - t0 |
| 213 | |
| 214 | assert result["commits_written"] == 500 |
| 215 | assert elapsed < 3.0, ( |
| 216 | f"apply_mpack (500-commit clone) took {elapsed:.3f}s — target < 3s" |
| 217 | ) |
File History
1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3
docs: docstring sprint contract→find-symbol — idiomatic run…
Sonnet 4.6
patch
145 days ago