sponge-compsearch / benchmark.py
EugeneBoondock's picture
Sponge online learners and the compression-scored search that found them
b999890 verified
Raw History Blame Contribute Delete
3.72 kB
"""Benchmark learners against standard compressors on three 1 MB streams.
search Python source the search optimised on
held-out Python source never used in the search
english English prose (Python's changelog): a kind of text the search never saw
python benchmark.py --run runs/first
python benchmark.py --run runs/first --out runs/first/benchmark.md
All numbers are bits per byte; lower is better. For the learners, the cost of their own
source code is included; the standard compressors are not charged for their program.
"""
from __future__ import annotations
import argparse
import bz2
import lzma
import time
import zlib
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from compsearch.evaluate import evaluate
from compsearch.search import load_results, ranked
from probe import read_streams
STREAMS = {"search": "data/search.bin", "held-out": "data/holdout.bin", "english": "data/english.bin"}
def standard_compressors() -> dict:
tools = {
"zlib -9 (LZ77, as in zip/gzip)": lambda data: zlib.compress(data, 9),
"bz2 -9 (block sorting)": lambda data: bz2.compress(data, 9),
"xz -9e (LZMA)": lambda data: lzma.compress(data, preset=9 | lzma.PRESET_EXTREME),
}
try:
import brotli
tools["brotli -11 (LZ77 + context modelling)"] = lambda data: brotli.compress(data, quality=11, lgwin=24)
except ImportError:
pass
return tools
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--run", default="runs/first")
parser.add_argument("--seconds", type=float, default=600.0)
parser.add_argument("--mem-mb", type=int, default=1024)
parser.add_argument("--out", help="also write the table to this Markdown file")
args = parser.parse_args()
read_streams() # makes data/english.bin if it is missing
data = {name: Path(path).read_bytes() for name, path in STREAMS.items()}
rows = []
for name, compress in standard_compressors().items():
cells, started = [], time.monotonic()
for stream in data.values():
cells.append(8 * len(compress(stream)) / len(stream))
speed = sum(len(s) for s in data.values()) / 1024 / (time.monotonic() - started)
rows.append((name, cells, speed))
run_dir = Path(args.run)
best = ranked(load_results(run_dir))[0]
learners = {
"order-0 seed (byte frequencies)": Path("seeds/order0.py"),
"sponge seed (hand-written start)": Path("seeds/sponge.py"),
f"best searched learner (#{best['id']})": run_dir / "cands" / f"{best['id']:05d}" / "learner.py",
}
for name, path in learners.items():
with ThreadPoolExecutor(max_workers=len(STREAMS)) as pool:
results = list(pool.map(lambda p: evaluate(path, p, args.seconds, args.mem_mb), STREAMS.values()))
cells = [r["bpb"] if r.get("status") == "ok" else float("nan") for r in results]
seconds = [r["seconds"] for r in results if r.get("status") == "ok"]
speed = sum(len(s) for s in data.values()) / 1024 / sum(seconds) if seconds else float("nan")
rows.append((name, cells, speed))
lines = ["| Model | " + " | ".join(STREAMS) + " | Speed (KB/s) |", "|---|" + "---|" * (len(STREAMS) + 1)]
for name, cells, speed in rows:
lines.append(f"| {name} | " + " | ".join(f"{cell:.3f}" for cell in cells) + f" | {speed:,.0f} |")
table = "\n".join(lines)
print(table)
if args.out:
Path(args.out).write_text(table + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())