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 <celino.jp11@gmail.com>
This commit is contained in:
Devin AI
2025-07-22 14:31:18 +00:00
co-authored by Jocelino Pereira
parent ca2ac82c83
commit 250db1dbd4
2 changed files with 156 additions and 3 deletions
+142
View File
@@ -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(&microVU0.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.
+14 -3
View File
@@ -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;