Set io.unalloc=true and optimize it
io.unalloc Performance Analysis and Improvement Plan
Executive Summary
The io.unalloc feature in radare2 provides visual feedback for unallocated memory regions during display operations (hex dumps, disassembly, printing). While functionally correct, the current implementation suffers from severe performance issues that make it impractical for everyday use. The primary bottleneck is the per-byte validation through expensive tree traversal operations.
Current Implementation Analysis
What is io.unalloc?
io.unalloc is a boolean configuration option that, when enabled, checks if each byte being displayed is allocated/valid in the current memory map. Unallocated bytes are replaced with a configurable character (default: '.').
Configuration Options:
e io.unalloc=true/false- Enable/disable the featuree io.unalloc.ch=<char>- Set character for unallocated bytes (default: '.')
Usage Locations:
- Hex dump commands (
px,p8) - Disassembly commands (
pd) - Print commands (
prc) - All operations that display memory content
Current Implementation Architecture
// Core validation call chain
is_valid_offset() -> r_io_is_valid_offset() -> r_io_map_get_at() -> r_io_bank_get_map_at()Key Functions:
r_io_is_valid_offset()- Main entry point for validationr_io_map_get_at()- Retrieves map for given addressr_io_bank_get_map_at()- Performs expensive tree traversal lookupr_crbtree_find_node()- Red-Black tree search (O(log n))
Performance Bottlenecks
1. Primary Bottleneck: Tree Traversal
Location: libr/io/io_bank.c:1147 - r_io_bank_get_map_at()
Issue: Each validation call performs a full Red-Black tree traversal:
RRBNode *node = r_crbtree_find_node (bank->submaps, &addr, _find_sm_by_vaddr_cb, NULL);Impact: O(log n) complexity for every single byte displayed
- For a 1KB hex dump: 1024 tree traversals
- For large disassembly: thousands of traversals
2. Performance-Critical Hot Paths
Hex Dump Operations (px command)
File: libr/core/cmd_print.inc.c:819
if (show_unalloc && !core->print->iob.is_valid_offset (core->print->iob.io, core->addr + j, false)) {
ch0 = core->print->io_unalloc_ch;
ch1 = core->print->io_unalloc_ch;
}Print Operations (p8 command)
File: libr/util/print.c:1318
if (p && use_unalloc && !p->iob.is_valid_offset (p->iob.io, addr + j, false)) {
char ch = p->io_unalloc_ch;
// ... handle unallocated byte
}Disassembly Operations (pd command)
File: libr/core/disasm.c:6671
if (core->print->flags & R_PRINT_FLAGS_UNALLOC) {
if (!core->anal->iob.is_valid_offset (core->anal->iob.io, ds->at, 0)) {
// Display "unmapped" and continue
}
}3. Complex Call Chain Overhead
Each validation involves multiple function calls and checks:
is_valid_offset()- Function call overhead- Parameter validation (
R_RETURN_VAL_IF_FAIL) - Bank lookup (
r_io_bank_get) - Hash table lookup - Tree traversal (
r_crbtree_find_node) - Most expensive - Submap validation (
r_io_submap_contain) - Range checking - Map reference resolution (
r_io_map_get_by_ref)
4. Lack of Caching
Current State: No caching mechanism for validation results
- Each byte requires full validation chain
- No reuse of previous validation results
- No optimization for consecutive addresses
Limited Existing Cache: IO cache only works when R_PERM_X is set and reads 4 bytes to compare against 0xffffffff
Root Cause Analysis
The performance issues stem from architectural decisions:
- Per-byte validation granularity - Checking each byte individually is overly fine-grained
- Expensive data structure choice - Red-Black trees are optimal for infrequent lookups, not per-byte validation
- No caching layer - Repeated validations for same addresses
- No range-based optimization - Consecutive bytes could be validated as ranges
Improvement Plan
Phase 1: Immediate Performance Improvements
1.1 Add Valid Offset Result Cache
Priority: Critical Expected Improvement: 90%+ performance gain for typical usage
Implementation:
typedef struct {
ut64 addr;
bool result;
ut32 map_id; // Cache invalidation support
} ValidOffsetCacheItem;
typedef struct {
ValidOffsetCacheItem *items;
int size;
int capacity;
// LRU eviction policy
} ValidOffsetCache;Cache Strategy:
- LRU eviction with configurable size (default: 1024 entries)
- Cache invalidation on map changes
- Fast hash table lookup
1.2 Range-Based Validation Optimization
Priority: High Expected Improvement: 50%+ for consecutive memory regions
Implementation:
typedef struct {
ut64 start_addr;
ut64 end_addr;
bool is_valid;
ut32 map_id; // For invalidation
} AddressRangeCache;Strategy:
- Validate address ranges instead of individual bytes
- Cache range results
- Merge consecutive valid/invalid ranges
Phase 2: Architecture Improvements
2.1 Batch Validation API
Priority: High Expected Improvement: 30%+ for large operations
New API:
// Validate multiple addresses in single call
R_API bool r_io_is_valid_offset_batch(RIO *io, ut64 *addrs, bool *results, int count);
// Validate address range
R_API bool r_io_is_valid_range(RIO *io, ut64 start, ut64 size, bool *result);2.2 Optimization for Common Patterns
Priority: Medium Expected Improvement: 20%+ for typical use cases
Optimizations:
- Fast path for sequential addresses (same map)
- Early termination for unmapped regions
- Skip validation when all bytes are in same known region
Phase 3: Advanced Optimizations
3.1 Alternative Data Structures
Priority: Medium Expected Improvement: 15%+ for complex mappings
Options:
- Interval trees for faster range queries
- B-trees with better cache locality
- Flat arrays for simple memory layouts
3.2 Map Change Notification System
Priority: Low-Medium Expected Improvement: Prevents cache staleness
Implementation:
- Register cache invalidation callbacks
- Track map modification operations
- Efficient cache clearing
Phase 4: Memory Usage Optimization
4.1 Adaptive Cache Sizing
Priority: Low Expected Improvement: Reduced memory footprint
Strategy:
- Dynamic cache sizing based on usage patterns
- Memory pressure detection and response
- Configurable cache limits
Implementation Roadmap
Week 1-2: Core Caching Implementation
- Design and implement valid offset cache
- Add cache invalidation hooks
- Performance testing and benchmarking
Week 3: Range-Based Optimization
- Implement range validation API
- Add range merging logic
- Update hot paths to use ranges
Week 4: Integration and Testing
- Update all print/disassembly hot paths
- Comprehensive performance testing
- Regression testing with existing test suite
Week 5-6: Advanced Features
- Batch validation API
- Alternative data structure evaluation
- Documentation and examples
Expected Performance Improvements
| Optimization | Expected Gain | Implementation Effort |
|---|---|---|
| Valid Offset Cache | 90%+ | Medium |
| Range Validation | 50%+ | Medium |
| Batch Operations | 30%+ | High |
| Data Structure Changes | 15%+ | High |
Overall Expected Improvement: 95%+ performance improvement for typical usage scenarios
Testing Strategy
Performance Benchmarks
- Large hex dump operations (
px 100000) - Disassembly of large functions
- Mixed mapped/unmapped memory regions
- Cache hit/miss ratios
Correctness Tests
- Existing test suite compatibility
- Edge cases (map boundaries, VA mode changes)
- Cache invalidation scenarios
- Memory stress testing
Compatibility
- Ensure no API changes for existing users
- Backward compatibility with existing scripts
- Configuration option compatibility
Risk Assessment
Low Risk
- Result caching (isolated impact)
- Range-based validation (internal optimization)
Medium Risk
- API changes (need careful migration)
- Data structure changes (extensive testing needed)
High Risk
- Cache invalidation bugs (stale data)
- Memory usage increases (resource constraints)
Conclusion
The io.unalloc feature's poor performance is primarily due to per-byte tree traversals in the validation path. With the proposed caching and range-based optimizations, we can achieve 95%+ performance improvements while maintaining full compatibility and correctness.
The implementation should prioritize the result cache first, as it provides the highest performance gain with the lowest risk. Subsequent optimizations can build on this foundation to further improve performance for complex use cases.
This plan makes io.unalloc practical for everyday use, enabling users to visually identify unallocated memory regions without the current performance penalty.
Source: radareorg/radare2