import subprocess
import json
import os
import sys
from pathlib import Path
def run_massif():
print("Running valgrind massif on profiling harness...")
subprocess.run(["cargo", "bench", "--bench", "profiling_harness", "--no-run"],
capture_output=True, check=True)
harness_path = Path("target/release/deps/profiling_harness-")
binaries = list(Path("target/release/deps").glob("profiling_harness-*"))
if not binaries:
print("Error: Could not find compiled benchmark binary")
sys.exit(1)
binary = binaries[0]
print(f"Using binary: {binary}")
cmd = [
"valgrind",
"--tool=massif",
"--massif-out-file=massif.out",
f"--max-snapshots=100",
str(binary),
]
print(f"Command: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print("Valgrind output:")
print(result.stdout)
print(result.stderr)
sys.exit(1)
print("\n" + "="*60)
print("MEMORY PROFILING RESULTS")
print("="*60 + "\n")
ms_result = subprocess.run(["ms_print", "massif.out"],
capture_output=True, text=True)
print(ms_result.stdout)
if os.path.exists("massif.out"):
os.remove("massif.out")
def analyze_record_structure():
print("\n" + "="*60)
print("ALLOCATION ANALYSIS FROM SOURCE CODE")
print("="*60 + "\n")
avg_tags_per_record = 35
avg_subfields_per_field = 6
avg_string_length_value = 50
string_overhead = 24
tag_allocs = avg_tags_per_record
tag_memory = tag_allocs * string_overhead
subfield_allocs = avg_tags_per_record * avg_subfields_per_field
subfield_memory = subfield_allocs * (string_overhead + avg_string_length_value)
field_vec_allocs = avg_tags_per_record
field_vec_overhead_per_vec = 24 field_vec_memory = field_vec_allocs * field_vec_overhead_per_vec
indexmap_allocs = 1
indexmap_memory = 200
print(f"Per-record allocation analysis:")
print(f" Tag allocations: {tag_allocs:3} (String overhead: {tag_memory:4} bytes)")
print(f" Subfield allocations: {subfield_allocs:3} (overhead + value: {subfield_memory:6} bytes)")
print(f" Field Vec allocations: {field_vec_allocs:3} (overhead: {field_vec_memory:4} bytes)")
print(f" IndexMap allocation: {indexmap_allocs:3} (overhead: {indexmap_memory:4} bytes)")
print(f" {'─' * 60}")
print(f" Total allocations: {tag_allocs + subfield_allocs + field_vec_allocs + indexmap_allocs:3}")
print(f" Total heap overhead: {tag_memory + subfield_memory + field_vec_memory + indexmap_memory:6} bytes")
print(f"\nOptimization potential (per-record):")
smallvec_saving_allocs = int(field_vec_allocs * 0.7)
smallvec_saving_bytes = smallvec_saving_allocs * field_vec_overhead_per_vec
print(f" SmallVec<[Subfield; 4]>: Save {smallvec_saving_allocs} allocs, {smallvec_saving_bytes} bytes")
tag_opt_saving_allocs = tag_allocs
tag_opt_saving_bytes = tag_memory - (tag_allocs * 2) print(f" Tags as u16: Save {tag_opt_saving_allocs} allocs, {tag_opt_saving_bytes} bytes")
total_savings_allocs = smallvec_saving_allocs + tag_opt_saving_allocs
total_savings_bytes = smallvec_saving_bytes + tag_opt_saving_bytes
print(f" Combined optimization: Save {total_savings_allocs} allocs, {total_savings_bytes} bytes/record")
print(f"\nFor 10,000 records:")
print(f" Allocation reduction: {total_savings_allocs * 10000:,} fewer allocations")
print(f" Memory reduction: {total_savings_bytes * 10000 / 1024 / 1024:.1f} MB")
if __name__ == "__main__":
print("MARC Record Allocation Profiling")
print("=" * 60)
try:
subprocess.run(["which", "valgrind"], capture_output=True, check=True)
analyze_record_structure()
except subprocess.CalledProcessError:
print("Note: valgrind not found, skipping runtime profiling")
print("Running analysis based on source code structure...\n")
analyze_record_structure()