-
-
Notifications
You must be signed in to change notification settings - Fork 2
Lazy Loading Guide
Simple Resume v0.1.1 introduces lazy loading for the generation module. This improves startup performance and reduces memory footprint for applications that do not always use resume generation.
Lazy loading defers importing heavy modules until they're actually needed, rather than loading everything at import time. This provides several benefits:
- Faster Startup: Applications start quicker since fewer modules are loaded initially
- Lower Memory Usage: Memory is consumed only by the functionality actively used.
- Better Performance: For applications that don't use generation, there's zero overhead
The main API uses lazy loading by default:
from simple_resume import generate_pdf, generate_html, generate_all
# Functions are lazy-loaded when first called
result = generate_pdf("resume.yaml", output_dir="output/")Suited for:
- CLI tools and scripts
- Applications where generation is optional
- Memory-constrained environments
For more control over lazy loading:
from simple_resume.shell.generate.lazy import generate_pdf, generate_html
# Generation modules only loaded when functions are called
result = generate_html("resume.yaml", preview_mode=True)Suited for:
- Explicit control over loading
- Mixed-use applications
- Performance-critical startup scenarios
For predictable performance when generation is always used:
from simple_resume.shell.generate.core import generate_pdf, generate_html
# All generation modules loaded immediately
result = generate_pdf("resume.yaml", output_dir="output/")Suited for:
- Web applications and services
- High-throughput generation scenarios
- Scenarios where predictable response times are critical
import time
# Test lazy loading
start = time.time()
from simple_resume import generate_pdf
lazy_import_time = time.time() - start
# Test eager loading
start = time.time()
from simple_resume.shell.generate.core import generate_pdf
eager_import_time = time.time() - start
print(f"Lazy import: {lazy_import_time:.4f}s")
print(f"Eager import: {eager_import_time:.4f}s")import psutil
import os
def get_memory_usage():
process = psutil.Process(os.getpid())
return process.memory_info().rss / 1024 / 1024 # MB
# Test memory with lazy loading
initial_memory = get_memory_usage()
from simple_resume import generate_pdf
lazy_memory = get_memory_usage()
# Test memory with eager loading
from simple_resume.shell.generate.core import generate_pdf
eager_memory = get_memory_usage()
print(f"Initial memory: {initial_memory:.1f} MB")
print(f"After lazy import: {lazy_memory:.1f} MB")
print(f"After eager import: {eager_memory:.1f} MB")Recommended: Use lazy loading
#!/usr/bin/env python3
"""CLI tool that generates resumes."""
import argparse
from simple_resume import generate_pdf, generate_html, generate_all, preview
def main():
parser = argparse.ArgumentParser(description="Generate resumes")
parser.add_argument("resume", help="Resume YAML file")
parser.add_argument("--format", choices=["pdf", "html", "all"], default="pdf")
parser.add_argument("--preview", action="store_true")
args = parser.parse_args()
if args.preview:
preview(args.resume)
elif args.format == "pdf":
generate_pdf(args.resume, output_dir="output/")
elif args.format == "html":
generate_html(args.resume, output_dir="output/")
else:
generate_all(args.resume, formats=["pdf", "html"], output_dir="output/")
if __name__ == "__main__":
main()Benefits:
- Fast startup even if generation isn't needed
- Low memory overhead
- Functions only loaded when actually used
Recommended: Use eager loading
"""FastAPI web service for resume generation."""
from fastapi import FastAPI, UploadFile
from simple_resume.shell.generate.core import generate_pdf, generate_html
from simple_resume.core.models import GenerationConfig
app = FastAPI()
@app.post("/generate/pdf")
async def generate_pdf_endpoint(file: UploadFile):
"""Generate PDF resume with predictable performance."""
# Generation modules already loaded, no delay
content = await file.read()
result = generate_pdf(content, output_dir="/tmp/output/")
return {"success": True, "output_path": result.output_path}
@app.post("/generate/html")
async def generate_html_endpoint(file: UploadFile):
"""Generate HTML resume with predictable performance."""
content = await file.read()
result = generate_html(content, preview_mode=True)
return {"success": True, "output_path": result.output_path}Benefits:
- Predictable response times
- No first-request delay
- Consistent performance
Recommended: Use lazy loading
"""Library that optionally provides resume generation."""
from simple_resume import generate_pdf, generate_html
class DocumentProcessor:
"""Processes documents with optional resume generation."""
def __init__(self):
# Generation modules available at module level
self._generation_loaded = False
def _load_generation(self):
"""Initialize generation functionality."""
if not self._generation_loaded:
# Functions already imported at module level
self.generate_pdf = generate_pdf
self.generate_html = generate_html
self._generation_loaded = True
def process_document(self, doc_path: str, generate_resume: bool = False):
"""Process a document, optionally generating resume."""
# Regular document processing
self._process_content(doc_path)
# Only load generation if needed
if generate_resume:
self._load_generation()
self.generate_pdf(doc_path, output_dir="output/")
def _process_content(self, doc_path: str):
"""Regular document processing logic."""
passBenefits:
- Zero overhead for users who don't need generation
- Conditional functionality loading
- Flexible API design
from simple_resume.shell.generate import core
from simple_resume.shell.generate.lazy import generate_pdf
class ResumeGenerator:
"""Resume generator with loading strategy options."""
def __init__(self, lazy: bool = True):
self.lazy = lazy
self._core = None
@property
def core(self):
"""Access core module when needed."""
if self._core is None and not self.lazy:
self._core = core
return self._core
def generate_pdf(self, *args, **kwargs):
"""Generate PDF with chosen loading strategy."""
if self.lazy:
return generate_pdf(*args, **kwargs)
else:
return self.core.generate_pdf(*args, **kwargs)import time
import tracemalloc
from simple_resume import generate_pdf
from simple_resume.shell.generate.core import generate_pdf as eager_pdf
def profile_generation_performance():
"""Profile different loading approaches."""
# Test lazy loading performance
tracemalloc.start()
lazy_start = time.time()
lazy_import_time = time.time() - lazy_start # Already imported
# First call (triggers lazy load)
call_start = time.time()
# Note: This would need actual resume data to work
# result = generate_pdf("test.yaml")
lazy_call_time = time.time() - call_start
current_memory, peak_memory = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Lazy Import Time: {lazy_import_time:.4f}s")
print(f"Lazy First Call: {lazy_call_time:.4f}s")
print(f"Memory Usage: {current_memory / 1024 / 1024:.1f} MB")
print(f"Peak Memory: {peak_memory / 1024 / 1024:.1f} MB")# Use lazy loading for:
# - CLI tools
# - Scripts
# - Optional functionality
from simple_resume import generate_pdf
# Use eager loading for:
# - Web services
# - High-throughput applications
# - Always-used functionality
from simple_resume.shell.generate.core import generate_pdffrom simple_resume import generate_pdf
try:
result = generate_pdf("resume.yaml")
except ImportError as e:
print(f"Generation functionality not available: {e}")
# Fallback behaviorimport time
import sys
def profile_imports():
"""Profile import performance for your use case."""
# Clear any existing imports
modules_to_remove = [k for k in sys.modules.keys() if "simple_resume" in k]
for module in modules_to_remove:
del sys.modules[module]
approaches = [
("Lazy", "from simple_resume import generate_pdf"),
("Eager", "from simple_resume.shell.generate.core import generate_pdf"),
("Explicit Lazy", "from simple_resume.shell.generate.lazy import generate_pdf"),
]
for name, import_stmt in approaches:
# Clear imports between tests
modules_to_remove = [k for k in sys.modules.keys() if "simple_resume" in k]
for module in modules_to_remove:
del sys.modules[module]
start = time.time()
exec(import_stmt)
end = time.time()
print(f"{name}: {end - start:.4f}s")from simple_resume import generate_pdf
from simple_resume.shell.generate.core import generate_pdf as eager_pdf
from simple_resume.shell.generate.lazy import generate_pdf as lazy_pdf
def test_generation_works():
"""Test that both lazy and eager loading work."""
# Test lazy loading
assert callable(generate_pdf)
# Test eager loading
assert callable(eager_pdf)
# Test explicit lazy loading
assert callable(lazy_pdf)
print("All loading approaches work correctly!")# Old approach (no longer available)
# import simple_resume.generation
# New approach (lazy - recommended)
from simple_resume import generate_pdf, generate_html
# Or new approach (eager - for web services)
from simple_resume.shell.generate.core import generate_pdf, generate_htmlimport time
import logging
from simple_resume import generate_pdf
# Set up performance logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def timed_generate_pdf(*args, **kwargs):
"""Generate PDF with timing."""
start = time.time()
result = generate_pdf(*args, **kwargs)
end = time.time()
logger.info(f"PDF generation took {end - start:.2f}s")
return result- ImportError: Ensure you're using the correct import paths
- Performance Issues: Try eager loading for consistent performance
- Memory Issues: Use lazy loading to reduce memory footprint
import sys
def debug_imports():
"""Debug what modules are loaded."""
print("Loaded modules:")
for module_name in sorted(sys.modules.keys()):
if "simple_resume" in module_name:
print(f" {module_name}")#!/usr/bin/env python3
"""Compare lazy vs eager loading performance."""
import time
import tracemalloc
def benchmark_approaches():
"""Benchmark different loading approaches."""
print("Simple Resume Loading Performance Benchmark")
print("=" * 50)
approaches = [
("Lazy Loading", "from simple_resume import generate_pdf"),
("Eager Loading", "from simple_resume.shell.generate.core import generate_pdf"),
("Explicit Lazy", "from simple_resume.shell.generate.lazy import generate_pdf"),
]
results = {}
for name, import_stmt in approaches:
# Clear any existing imports
modules_to_remove = [k for k in sys.modules.keys() if "simple_resume" in k]
for module in modules_to_remove:
del sys.modules[module]
# Benchmark import time
tracemalloc.start()
start = time.time()
exec(import_stmt)
import_time = time.time() - start
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
results[name] = {
"import_time": import_time,
"memory_kb": current / 1024,
"peak_memory_kb": peak / 1024
}
print(f"{name}:")
print(f" Import Time: {import_time:.4f}s")
print(f" Memory: {current / 1024:.1f} KB")
print(f" Peak Memory: {peak / 1024:.1f} KB")
print()
return results
if __name__ == "__main__":
benchmark_approaches()Lazy loading in Simple Resume provides flexibility for different use cases:
- Use lazy loading for CLI tools and optional functionality
- Use eager loading for web services and always-used features
- Profile your application to choose the best approach
The default API uses lazy loading to provide good performance for most use cases while maintaining backward compatibility.