SmolVM is optimized for low-latency agent workflows, providing sub-second boot times and minimal overhead for command execution.
Benchmark Results
Latest lifecycle timings (p50) on a standard Linux host:
| Phase | Time |
|---|
| Create + Start | ~572ms |
| SSH ready | ~2.1s |
| Command execution | ~43ms |
| Stop + Delete | ~751ms |
| Full lifecycle (boot → run → teardown) | ~3.5s |
Benchmarks measured on AMD Ryzen 7 7800X3D (8C/16T), Ubuntu Linux, KVM/Firecracker backend.
Running Your Own Benchmarks
You can benchmark SmolVM on your hardware using the included benchmark script:
python scripts/benchmarks/bench_subprocess.py --vms 10 -v
This will create, start, run commands in, and teardown 10 VMs sequentially, reporting detailed timing metrics.
- MicroVM Creation: SmolVM allocates IP addresses, TAP devices, and network rules in ~572ms
- SSH Ready Time: The VM boots and becomes accessible via SSH in ~2.1s total
- Hardware Virtualization: Uses KVM on Linux and Hypervisor.framework on macOS for near-native performance
- Command Execution: ~43ms average latency for running commands via SSH
- Memory Overhead: Minimal host overhead beyond configured VM memory (default 512MB)
- CPU Efficiency: Hardware virtualization provides near-native CPU performance
- Graceful Shutdown: Firecracker VMs can be stopped in ~751ms
- Resource Cleanup: Network rules, TAP devices, and disk images are cleaned up automatically
- Fast Path for Ephemeral VMs: SIGKILL-based teardown for sandbox VMs that don’t need state preservation
Optimization Tips
1. Reuse VMs for Multiple Commands
Instead of creating a new VM for each command, reuse the same VM:
from smolvm import SmolVM
with SmolVM() as vm:
vm.start()
# Run multiple commands on the same VM
result1 = vm.run("pip install requests")
result2 = vm.run("python script.py")
result3 = vm.run("cat output.txt")
This amortizes the ~2.1s boot time across multiple operations.
2. Use Appropriate Resource Allocation
Configure CPU and memory based on your workload:
from smolvm import SmolVM, VMConfig
# Lightweight workload
config = VMConfig(
vcpu_count=1,
mem_size_mib=256,
disk_size_mib=2048
)
# Heavy workload
config = VMConfig(
vcpu_count=4,
mem_size_mib=2048,
disk_size_mib=8192
)
Over-allocating resources can lead to host memory pressure and slower performance.
3. Pre-built Custom Images
For workloads requiring specific dependencies, build a custom rootfs image with pre-installed packages:
from smolvm.build import RootfsBuilder
builder = RootfsBuilder()
builder.add_package("python3-requests")
builder.add_package("python3-numpy")
rootfs_path = builder.build()
# Use the custom image
config = VMConfig(rootfs_path=rootfs_path)
vm = SmolVM(config)
This eliminates the need to install packages at runtime.
4. Shared vs Isolated Disk Mode
Choose the appropriate disk mode for your use case:
Isolated Mode (default): Each VM gets its own copy of the rootfs
- ✅ Complete isolation between VMs
- ✅ No cross-VM contamination
- ❌ Higher disk usage
- ❌ Copy overhead on first boot
Shared Mode: All VMs use the same rootfs image
- ✅ No disk copy overhead
- ✅ Lower disk usage
- ❌ Changes persist across VMs
- ❌ Potential cross-VM contamination
config = VMConfig(disk_mode="shared") # For read-only workloads
5. Backend Selection
SmolVM supports multiple backends with different performance characteristics:
- Firecracker (Linux): Fastest boot times, lowest overhead, recommended for production
- QEMU (macOS/Linux): Broader compatibility, slightly higher overhead
from smolvm import SmolVM
# Explicitly choose backend
vm = SmolVM(backend="firecracker") # Linux only
vm = SmolVM(backend="qemu") # macOS/Linux
Check VM Status
from smolvm import SmolVM
manager = SmolVM()
vms = manager.list_vms()
for vm in vms:
print(f"VM {vm.vm_id}: {vm.status}")
print(f" PID: {vm.pid}")
print(f" IP: {vm.network.guest_ip if vm.network else 'N/A'}")
Reconcile Stale VMs
Detect and clean up VMs marked as RUNNING but with dead processes:
manager = SmolVM()
stale_vms = manager.reconcile()
if stale_vms:
print(f"Found {len(stale_vms)} stale VMs")
for vm_id in stale_vms:
manager.delete(vm_id)
Scalability Considerations
IP Address Pool
By default, SmolVM allocates IPs from 172.16.0.2 to 172.16.0.254, supporting 253 concurrent VMs.
SSH Port Pool
Host-side SSH forwarding uses ports 2200-2999, supporting 800 concurrent VMs.
System Limits
Check your system’s ulimit for open files and processes:
# Check file descriptor limit
ulimit -n
# Check process limit
ulimit -u
# Increase limits (add to /etc/security/limits.conf)
* soft nofile 65536
* hard nofile 65536
Profiling Tips
Measure Individual Phases
import time
from smolvm import SmolVM, VMConfig
config = VMConfig()
# Create phase
start = time.time()
manager = SmolVM()
vm_info = manager.create(config)
print(f"Create: {time.time() - start:.3f}s")
# Start phase
start = time.time()
manager.start(vm_info.vm_id)
print(f"Start: {time.time() - start:.3f}s")
# Command execution
from smolvm import VM
vm = VM.from_id(vm_info.vm_id)
start = time.time()
vm.run("echo hello")
print(f"Command: {time.time() - start:.3f}s")
# Cleanup
start = time.time()
manager.stop(vm_info.vm_id)
manager.delete(vm_info.vm_id)
print(f"Teardown: {time.time() - start:.3f}s")
Network Latency
Measure network roundtrip time:
from smolvm import SmolVM
with SmolVM() as vm:
vm.start()
# Ping test from host to guest
result = vm.run("echo pong")
print(f"SSH roundtrip: {result.duration_ms:.1f}ms")