feat: Remove outdated performance analysis documents and update optimization summaries for dungeon and overworld loading
This commit is contained in:
@@ -14,217 +14,48 @@
|
||||
|
||||
### 1. **Performance Monitoring System with Feature Flag**
|
||||
|
||||
#### **Features Added**
|
||||
- **Feature Flag Control**: `kEnablePerformanceMonitoring` in FeatureFlags
|
||||
- **Zero-Overhead When Disabled**: ScopedTimer becomes no-op when monitoring is off
|
||||
- **UI Toggle**: Performance monitoring can be enabled/disabled in Settings
|
||||
|
||||
#### **Implementation**
|
||||
```cpp
|
||||
// Feature flag integration
|
||||
ScopedTimer::ScopedTimer(const std::string& operation_name)
|
||||
: operation_name_(operation_name),
|
||||
enabled_(core::FeatureFlags::get().kEnablePerformanceMonitoring) {
|
||||
if (enabled_) {
|
||||
PerformanceMonitor::Get().StartTimer(operation_name_);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Feature Flag Control**: `kEnablePerformanceMonitoring` in FeatureFlags allows enabling/disabling the system.
|
||||
- **Zero-Overhead When Disabled**: `ScopedTimer` becomes a no-op when monitoring is off.
|
||||
- **UI Toggle**: Performance monitoring can be toggled in the Settings UI.
|
||||
|
||||
### 2. **DungeonEditor Parallel Loading (79% Speedup)**
|
||||
|
||||
#### **Problem Solved**
|
||||
- **DungeonEditor::LoadAllRooms**: 17,966ms → 3,746ms
|
||||
- Loading 296 rooms sequentially was the primary bottleneck
|
||||
|
||||
#### **Solution: Multi-Threaded Room Loading**
|
||||
```cpp
|
||||
// Parallel processing with up to 8 threads
|
||||
const int max_concurrency = std::min(8, std::thread::hardware_concurrency());
|
||||
const int rooms_per_thread = (296 + max_concurrency - 1) / max_concurrency;
|
||||
|
||||
// Each thread processes ~37 rooms independently
|
||||
for (int i = start_room; i < end_room; ++i) {
|
||||
rooms[i] = zelda3::LoadRoomFromRom(rom_, i);
|
||||
rooms[i].LoadObjects();
|
||||
// ... other room processing
|
||||
}
|
||||
```
|
||||
|
||||
#### **Key Features**
|
||||
- **Thread-Safe Result Collection**: Mutex-protected shared data structures
|
||||
- **Hardware-Aware**: Automatically adapts to available CPU cores
|
||||
- **Error Handling**: Proper status propagation per thread
|
||||
- **Result Synchronization**: Main thread processes collected results
|
||||
- **Problem Solved**: Loading 296 rooms sequentially was the primary bottleneck, taking ~18 seconds.
|
||||
- **Solution**: Implemented multi-threaded room loading, using up to 8 threads to process rooms in parallel. This includes thread-safe collection of results and hardware-aware concurrency.
|
||||
|
||||
### 3. **Incremental Overworld Map Loading**
|
||||
|
||||
#### **Problem Solved**
|
||||
- Blank maps visible during loading
|
||||
- All maps loaded upfront causing UI blocking
|
||||
|
||||
#### **Solution: Priority-Based Incremental Loading**
|
||||
```cpp
|
||||
// Increased from 2 to 8 textures per frame
|
||||
const int textures_per_frame = 8;
|
||||
|
||||
// Priority system: current world maps first
|
||||
if (is_current_world || processed < textures_per_frame / 2) {
|
||||
Renderer::Get().RenderBitmap(*it);
|
||||
processed++;
|
||||
}
|
||||
```
|
||||
|
||||
#### **Key Features**
|
||||
- **Priority Loading**: Current world maps load first
|
||||
- **4x Faster Texture Creation**: 8 textures per frame vs 2
|
||||
- **Loading Indicators**: "Loading..." placeholders for pending maps
|
||||
- **Graceful Degradation**: Only draws maps with textures
|
||||
- **Problem Solved**: UI would block and show blank maps while all 160 overworld maps were loaded upfront.
|
||||
- **Solution**: Implemented a priority-based incremental loading system. It creates textures for the current world's maps first, at a 4x faster rate (8 per frame), while showing "Loading..." placeholders for the rest.
|
||||
|
||||
### 4. **On-Demand Map Reloading**
|
||||
|
||||
#### **Problem Solved**
|
||||
- Full map refresh on every property change
|
||||
- Expensive rebuilds for non-visible maps
|
||||
- **Problem Solved**: Any property change would trigger an expensive full map refresh, even for non-visible maps.
|
||||
- **Solution**: An intelligent refresh system now only reloads maps that are currently visible. Changes to non-visible maps are deferred until they are viewed.
|
||||
|
||||
#### **Solution: Intelligent Refresh System**
|
||||
```cpp
|
||||
void RefreshOverworldMapOnDemand(int map_index) {
|
||||
// Only refresh visible maps immediately
|
||||
bool is_current_map = (map_index == current_map_);
|
||||
bool is_current_world = (map_index / 0x40 == current_world_);
|
||||
|
||||
if (!is_current_map && !is_current_world) {
|
||||
// Defer refresh for non-visible maps
|
||||
maps_bmp_[map_index].set_modified(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Immediate refresh for visible maps
|
||||
RefreshChildMapOnDemand(map_index);
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
#### **Key Features**
|
||||
- **Visibility-Aware**: Only refreshes visible maps immediately
|
||||
- **Deferred Processing**: Non-visible maps marked for later refresh
|
||||
- **Selective Updates**: Only rebuilds changed components
|
||||
- **Smart Sibling Handling**: Large map siblings refreshed intelligently
|
||||
## Appendix A: Dungeon Editor Parallel Optimization
|
||||
|
||||
## 🎯 **Technical Architecture**
|
||||
- **Problem Identified**: `DungeonEditor::LoadAllRooms` took **17.97 seconds**, accounting for 99.9% of loading time.
|
||||
- **Strategy**: The 296 independent rooms were loaded in parallel across up to 8 threads (~37 rooms per thread).
|
||||
- **Implementation**: Used `std::async` to launch tasks and `std::mutex` to safely collect results (like room size and palette data). Results are sorted on the main thread for consistency.
|
||||
- **Result**: Loading time for the dungeon editor was reduced by **79%** to ~3.7 seconds.
|
||||
|
||||
### **Performance Monitoring System**
|
||||
```
|
||||
FeatureFlags::kEnablePerformanceMonitoring
|
||||
↓ (enabled/disabled)
|
||||
ScopedTimer (no-op when disabled)
|
||||
↓ (when enabled)
|
||||
PerformanceMonitor::StartTimer/EndTimer
|
||||
↓
|
||||
Operation timing collection
|
||||
↓
|
||||
Performance summary output
|
||||
```
|
||||
---
|
||||
|
||||
### **Parallel Loading Architecture**
|
||||
```
|
||||
Main Thread
|
||||
↓
|
||||
Spawn 8 Worker Threads
|
||||
↓ (parallel)
|
||||
Thread 1: Rooms 0-36 Thread 2: Rooms 37-73 ... Thread 8: Rooms 259-295
|
||||
↓ (thread-safe collection)
|
||||
Mutex-Protected Results
|
||||
↓ (main thread)
|
||||
Result Processing & Sorting
|
||||
↓
|
||||
Map Population
|
||||
```
|
||||
## Appendix B: Overworld Load Optimization
|
||||
|
||||
### **Incremental Loading Flow**
|
||||
```
|
||||
ROM Load Start
|
||||
↓
|
||||
Essential Maps (8 per world) → Immediate Texture Creation
|
||||
Non-Essential Maps → Deferred Texture Creation
|
||||
↓ (per frame)
|
||||
ProcessDeferredTextures()
|
||||
↓ (priority-based)
|
||||
Current World Maps First → Other Maps
|
||||
↓
|
||||
Loading Indicators for Pending Maps
|
||||
```
|
||||
- **Problem Identified**: `Overworld::Load` took **2.9 seconds**, with the main bottleneck being the sequential decompression of 160 map tiles (`DecompressAllMapTiles`).
|
||||
- **Strategy**: Parallelize the decompression operations and implement lazy loading for maps that are not immediately visible.
|
||||
- **Implementation**: The plan involves using `std::async` to decompress map batches concurrently and creating a system to only load essential maps on startup, deferring the rest to a background process.
|
||||
- **Expected Result**: A 70-80% reduction in initial overworld loading time.
|
||||
|
||||
## 📈 **Performance Impact Analysis**
|
||||
---
|
||||
|
||||
### **DungeonEditor Optimization**
|
||||
- **Before**: 17,967ms (single-threaded)
|
||||
- **After**: 3,747ms (8-threaded)
|
||||
- **Speedup**: 4.8x theoretical, 4.0x actual (due to overhead)
|
||||
- **Efficiency**: 83% of theoretical maximum
|
||||
## Appendix C: Renderer Optimization
|
||||
|
||||
### **OverworldEditor Optimization**
|
||||
- **Loading Time**: Reduced from blocking to progressive
|
||||
- **Texture Creation**: 4x faster (8 vs 2 per frame)
|
||||
- **User Experience**: No more blank maps, smooth loading
|
||||
- **Memory Usage**: Reduced initial footprint
|
||||
|
||||
### **Overall System Impact**
|
||||
- **Total Loading Time**: 18.6s → 4.7s (75% reduction)
|
||||
- **UI Responsiveness**: Near-instant vs 18-second freeze
|
||||
- **Memory Efficiency**: Reduced initial allocations
|
||||
- **CPU Utilization**: Better multi-core usage
|
||||
|
||||
## 🔧 **Configuration Options**
|
||||
|
||||
### **Performance Monitoring**
|
||||
```cpp
|
||||
// Enable/disable in UI or code
|
||||
FeatureFlags::get().kEnablePerformanceMonitoring = true/false;
|
||||
|
||||
// Zero overhead when disabled
|
||||
ScopedTimer timer("Operation"); // No-op when monitoring disabled
|
||||
```
|
||||
|
||||
### **Parallel Loading Tuning**
|
||||
```cpp
|
||||
// Adjust thread count based on system
|
||||
constexpr int kMaxConcurrency = 8; // Reasonable default
|
||||
const int max_concurrency = std::min(kMaxConcurrency,
|
||||
std::thread::hardware_concurrency());
|
||||
```
|
||||
|
||||
### **Incremental Loading Tuning**
|
||||
```cpp
|
||||
// Adjust textures per frame based on performance
|
||||
const int textures_per_frame = 8; // Balance between speed and UI responsiveness
|
||||
```
|
||||
|
||||
## 🎯 **Future Optimization Opportunities**
|
||||
|
||||
### **Potential Further Improvements**
|
||||
1. **Memory-Mapped ROM Access**: Reduce memory copying during loading
|
||||
2. **Background Thread Pool**: Reuse threads across operations
|
||||
3. **Predictive Loading**: Load likely-to-be-accessed maps in advance
|
||||
4. **Compression Caching**: Cache decompressed data for faster subsequent loads
|
||||
5. **GPU-Accelerated Texture Creation**: Move texture creation to GPU
|
||||
|
||||
### **Monitoring and Profiling**
|
||||
1. **Real-Time Performance Metrics**: In-app performance dashboard
|
||||
2. **Memory Usage Tracking**: Monitor memory allocations during loading
|
||||
3. **Thread Utilization Metrics**: Track CPU core usage efficiency
|
||||
4. **User Interaction Timing**: Measure time to interactive
|
||||
|
||||
## ✅ **Success Metrics Achieved**
|
||||
|
||||
- ✅ **75% reduction** in total loading time (18.6s → 4.7s)
|
||||
- ✅ **79% improvement** in DungeonEditor loading (17.9s → 3.7s)
|
||||
- ✅ **Zero-overhead** performance monitoring when disabled
|
||||
- ✅ **Smooth incremental loading** with visual feedback
|
||||
- ✅ **Intelligent on-demand refreshing** for better responsiveness
|
||||
- ✅ **Multi-threaded architecture** utilizing all CPU cores
|
||||
- ✅ **Backward compatibility** maintained throughout
|
||||
|
||||
## 🚀 **Result: Lightning-Fast YAZE**
|
||||
|
||||
YAZE has been transformed from a slow-loading application with 18-second freezes to a **lightning-fast ROM editor** that loads in under 5 seconds with smooth, progressive loading and intelligent resource management. The optimizations provide both immediate performance gains and a foundation for future enhancements.
|
||||
- **Problem Identified**: The original renderer created GPU textures synchronously on the main thread for all 160 overworld maps, blocking the UI for several seconds.
|
||||
- **Strategy**: Defer texture creation. Bitmaps and surface data are prepared first (a CPU-bound task that can be backgrounded), while the actual GPU texture creation (a main-thread-only task) is done progressively or on-demand.
|
||||
- **Implementation**: A `CreateBitmapWithoutTexture` method was introduced. A lazy loading system (`ProcessDeferredTextures`) processes a few textures per frame to avoid blocking, and `EnsureMapTexture` creates a texture immediately if a map becomes visible.
|
||||
- **Result**: A much more responsive UI during ROM loading, with an initial load time of only ~200-500ms.
|
||||
Reference in New Issue
Block a user