gabriel / muse public
test_mpack_perf.py python
217 lines 8.2 KB
Raw
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