This benchmark measures the performance impact of the chunked reading optimization in GH PR #119204 for the pickle module.
The PR adds chunked reading (1MB chunks) to prevent memory exhaustion when unpickling large objects:
# Run full benchmark suite (1MiB → 200MiB, takes several minutes) build/python Tools/picklebench/memory_dos_impact.py # Test just a few sizes (quick test: 1, 10, 50 MiB) build/python Tools/picklebench/memory_dos_impact.py --sizes 1 10 50 # Test smaller range for faster results build/python Tools/picklebench/memory_dos_impact.py --sizes 1 5 10 # Output as markdown for reports build/python Tools/picklebench/memory_dos_impact.py --format markdown > results.md # Test with protocol 4 instead of 5 build/python Tools/picklebench/memory_dos_impact.py --protocol 4
Note: Sizes are specified in MiB. Use --sizes 1 2 5 for 1MiB, 2MiB, 5MiB objects.
The --antagonistic flag tests malicious pickles that demonstrate the memory DoS protection:
# Quick DoS protection test (claims 10, 50, 100 MB but provides 1KB) build/python Tools/picklebench/memory_dos_impact.py --antagonistic --sizes 10 50 100 # Full DoS test (default: 10, 50, 100, 500, 1000, 5000 MB claimed) build/python Tools/picklebench/memory_dos_impact.py --antagonistic
Unlike normal benchmarks that test legitimate pickles, antagonistic mode tests:
Key difference:
100MB Claimed (actual: 1KB)
binbytes8_100MB_claim
Peak memory: 1.00 MB (claimed: 100 MB, saved: 99.00 MB, 99.0%)
Error: UnpicklingError ← Expected!
Summary:
Average claimed: 126.2 MB
Average peak: 0.54 MB
Average saved: 125.7 MB (99.6% reduction)
Protection Status: ✓ Memory DoS attacks mitigated by chunked reading
Before PR: Would allocate full claimed size (100MB+), potentially crash After PR: Allocates 1MB chunks, fails fast with minimal memory
This demonstrates the security improvement - protection against memory exhaustion attacks.
The benchmark includes an automatic comparison feature that runs the same tests on both a baseline and current Python build.
Build both versions, then use --baseline to automatically compare:
# Build the baseline (main branch without PR) git checkout main mkdir -p build-main cd build-main && ../configure && make -j $(nproc) && cd .. # Build the current version (with PR) git checkout unpickle-overallocate mkdir -p build cd build && ../configure && make -j $(nproc) && cd .. # Run automatic comparison (quick test with a few sizes) build/python Tools/picklebench/memory_dos_impact.py \ --baseline build-main/python \ --sizes 1 10 50 # Full comparison (all default sizes) build/python Tools/picklebench/memory_dos_impact.py \ --baseline build-main/python
The comparison output shows:
Save results separately and compare manually:
# Baseline results build-main/python Tools/picklebench/memory_dos_impact.py --format json > baseline.json # Current results build/python Tools/picklebench/memory_dos_impact.py --format json > current.json # Manual comparison diff -y <(jq '.' baseline.json) <(jq '.' current.json)
The default test suite includes:
Note: The full suite may require more than 16GiB of RAM.
| Test | What It Stresses |
|---|---|
bytes_* | BINBYTES8 opcode, raw binary data |
string_ascii_* | BINUNICODE8 with simple ASCII |
string_utf8_* | BINUNICODE8 with multibyte UTF-8 (€ chars) |
bytearray_* | BYTEARRAY8 opcode (protocol 5) |
list_large_items_* | Multiple chunked reads in sequence |
dict_large_values_* | Chunking in dict deserialization |
nested_* | Realistic mixed data structures |
tuple_* | Immutable structures |
# Test only 5MiB and 10MiB objects build/python Tools/picklebench/memory_dos_impact.py --sizes 5 10 # Test large objects: 50, 100, 200 MiB build/python Tools/picklebench/memory_dos_impact.py --sizes 50 100 200
# Run 10 iterations per test for better statistics build/python Tools/picklebench/memory_dos_impact.py --iterations 10 --sizes 1 10
# Generate JSON for programmatic analysis build/python Tools/picklebench/memory_dos_impact.py --format json | python -m json.tool
The peak memory metric shows the maximum memory allocated during unpickling:
Without chunking: Allocates full size immediately
With chunking: Allocates in 1MB chunks, grows geometrically
On a system with the PR applied, you should see:
1.00MiB Test Results bytes_1.00MiB: ~0.3ms, 1.00MiB peak (just at threshold) 2.00MiB Test Results bytes_2.00MiB: ~0.8ms, 2.00MiB peak (chunked: 1MiB → 2MiB) 10.00MiB Test Results bytes_10.00MiB: ~3-5ms, 10.00MiB peak (chunked: 1→2→4→8→10 MiB)
Time overhead is minimal (~10-20% for very large objects), but memory safety is significantly improved.