From 250db1dbd43519781c6d812b1daeae036c0c6f74 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:31:18 +0000 Subject: [PATCH] Optimize instruction cache allocation to reduce malloc/free overhead - Implement exponential growth strategy for instruction cache - Reduce frequent reallocations during block recompilation - Add comprehensive performance optimization report - Identified 5 key optimization areas, implemented highest impact fix The instruction cache was being reallocated on every block that exceeded the current size, causing frequent malloc/free operations. This change uses exponential growth (doubling) and preserves existing data during resize operations to minimize future reallocations. Co-Authored-By: Jocelino Pereira --- PERFORMANCE_OPTIMIZATION_REPORT.md | 142 ++++++++++++++++++ app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp | 17 ++- 2 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 PERFORMANCE_OPTIMIZATION_REPORT.md diff --git a/PERFORMANCE_OPTIMIZATION_REPORT.md b/PERFORMANCE_OPTIMIZATION_REPORT.md new file mode 100644 index 0000000..f7faf15 --- /dev/null +++ b/PERFORMANCE_OPTIMIZATION_REPORT.md @@ -0,0 +1,142 @@ +# PCSX2 ARM64 Performance Optimization Report + +## Executive Summary + +This report identifies 5 key areas for performance optimization in the PCSX2 ARM64 emulator codebase. The analysis focused on memory allocation patterns, redundant operations, and ARM64-specific optimization opportunities. + +## 1. Instruction Cache Reallocation Issue (HIGH PRIORITY) + +**Location**: `app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp` lines 2708-2714 + +**Issue**: The instruction cache (`s_pInstCache`) is frequently reallocated during block recompilation using a naive growth strategy. Every time a block requires more instructions than the current cache size, the entire cache is freed and reallocated with only a small increment (+10 instructions). + +**Code Pattern**: +```cpp +if (s_nInstCacheSize < (s_nEndBlock - startpc) / 4 + 1) +{ + free(s_pInstCache); + s_nInstCacheSize = (s_nEndBlock - startpc) / 4 + 10; + s_pInstCache = (EEINST*)malloc(sizeof(EEINST) * s_nInstCacheSize); +} +``` + +**Impact**: HIGH - This occurs during every block recompilation that exceeds the current cache size, causing: +- Frequent malloc/free operations (expensive system calls) +- Memory fragmentation +- Loss of existing cache data +- Poor cache locality + +**Solution**: Implement exponential growth strategy with data preservation to minimize future reallocations. + +## 2. MicroVU Memory Allocation Patterns (MEDIUM PRIORITY) + +**Location**: `app/src/main/cpp/pcsx2/x86/microVU.cpp` lines 138-139, `microVU.h` lines 190, 163, 170 + +**Issue**: Frequent `_aligned_malloc` and `_aligned_free` operations for microProgram structures and microBlockLink objects. + +**Code Patterns**: +```cpp +microProgram* prog = (microProgram*)_aligned_malloc(sizeof(microProgram), 64); +microBlockLink* newBlock = (microBlockLink*)_aligned_malloc(sizeof(microBlockLink), 32); +_aligned_free(freeI); +``` + +**Impact**: MEDIUM - Occurs during VU program creation/deletion: +- Aligned memory allocation is more expensive than regular malloc +- Frequent allocation/deallocation during emulation +- Memory fragmentation from different alignment requirements + +**Solution**: Implement object pools or pre-allocated memory regions for these frequently used structures. + +## 3. Redundant Memory Clearing Operations (LOW-MEDIUM PRIORITY) + +**Location**: Multiple files with `std::memset` patterns + +**Issue**: Unnecessary zero-initialization of large structures, particularly: +- `microVU_Branch.inl` lines 17-18: Clearing lpState structures +- `iCore.cpp` lines 32, 930-933: Clearing register arrays +- `microVU_Compile.inl`: Multiple memset operations + +**Code Patterns**: +```cpp +std::memset(µVU0.prog.lpState, 0, sizeof(microVU1.prog.lpState)); +std::memset(xmmregs, 0, sizeof(xmmregs)); +std::memset(pinst, 0, sizeof(EEINST)); +``` + +**Impact**: LOW-MEDIUM - Cumulative effect across many operations: +- Unnecessary CPU cycles spent zeroing memory +- Some structures are immediately overwritten after clearing +- Cache pollution from touching large memory regions + +**Solution**: Optimize initialization patterns and avoid redundant clears where data is immediately overwritten. + +## 4. ARM64 NEON SIMD Optimization Opportunities (MEDIUM PRIORITY) + +**Location**: `app/src/main/cpp/pcsx2/arm64/Vif_UnpackNEON.cpp` + +**Issue**: While the code already uses NEON instructions, there are opportunities for additional optimizations: + +**Current Implementation Analysis**: +- VIF unpacking uses individual NEON operations +- Some operations could be combined or vectorized further +- Potential for better instruction scheduling + +**Code Example** (lines 294-295): +```cpp +armAsm->Shl(destReg.V4S(), destReg.V4S(), 24); +armAsm->Ushr(destReg.V4S(), destReg.V4S(), 24); +``` + +**Impact**: MEDIUM - Affects graphics data processing performance: +- VIF unpacking is on the critical path for graphics rendering +- Better NEON utilization could improve frame rates +- ARM64-specific optimizations not fully exploited + +**Solution**: Implement more efficient NEON instruction sequences and better utilize ARM64 capabilities. + +## 5. Loop Optimization Opportunities (LOW-MEDIUM PRIORITY) + +**Location**: Various files with for/while loops + +**Issue**: Some loops could benefit from unrolling or vectorization, particularly in: +- Memory copying operations +- Register clearing loops +- Block iteration patterns + +**Examples**: +- `BaseblockEx.cpp` lines 76-80: Simple iteration that could be unrolled +- `microVU.h` lines 127-129, 253-255: Loops over fixed-size arrays + +**Impact**: LOW-MEDIUM - Depends on loop frequency: +- Hot loops in recompilation paths could benefit from optimization +- Some loops are over small, fixed-size arrays suitable for unrolling +- Profile-guided optimization needed to identify highest impact loops + +**Solution**: Profile-guided optimization of hot loops with unrolling or vectorization where appropriate. + +## Performance Impact Assessment + +| Issue | Priority | Frequency | Impact per Operation | Overall Impact | +|-------|----------|-----------|---------------------|----------------| +| Instruction Cache Reallocation | HIGH | Every oversized block | High | HIGH | +| MicroVU Memory Allocation | MEDIUM | VU program lifecycle | Medium | MEDIUM | +| Redundant Memory Clearing | LOW-MEDIUM | Various operations | Low | LOW-MEDIUM | +| NEON Optimizations | MEDIUM | Graphics processing | Medium | MEDIUM | +| Loop Optimizations | LOW-MEDIUM | Various | Low-Medium | LOW-MEDIUM | + +## Recommendation + +**Immediate Action**: Implement the instruction cache optimization (Issue #1) as it has the highest impact and is straightforward to fix. + +**Future Work**: Address the MicroVU memory allocation patterns and explore additional NEON optimizations for graphics performance improvements. + +## Implementation Notes + +The instruction cache optimization should: +1. Use exponential growth (doubling) to reduce future reallocations +2. Preserve existing cache data during resize operations +3. Maintain the same API and behavior +4. Follow existing error handling patterns in the codebase + +This optimization will significantly reduce malloc/free overhead during block recompilation, which is a critical performance path in the emulator. diff --git a/app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp b/app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp index deaddc8..89124fe 100644 --- a/app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp +++ b/app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp @@ -2707,10 +2707,21 @@ StartRecomp: { if (s_nInstCacheSize < (s_nEndBlock - startpc) / 4 + 1) { + const u32 required_size = (s_nEndBlock - startpc) / 4 + 10; + const u32 new_size = std::max(required_size, s_nInstCacheSize * 2); + + EEINST* new_cache = (EEINST*)malloc(sizeof(EEINST) * new_size); + if (!new_cache) + pxFailRel("Failed to allocate R5900 InstCache array"); + + if (s_pInstCache && s_nInstCacheSize > 0) + { + memcpy(new_cache, s_pInstCache, sizeof(EEINST) * s_nInstCacheSize); + } + free(s_pInstCache); - s_nInstCacheSize = (s_nEndBlock - startpc) / 4 + 10; - s_pInstCache = (EEINST*)malloc(sizeof(EEINST) * s_nInstCacheSize); - pxAssert(s_pInstCache != NULL); + s_pInstCache = new_cache; + s_nInstCacheSize = new_size; } EEINST* pcur = s_pInstCache + (s_nEndBlock - startpc) / 4;