© 2026 Hedgehog Software, LLC

TwitterGitHubDiscord
More
CommunitiesDocsAboutTermsPrivacy
Search
Star
Setup for Free
C#C
C#•15mo ago•
258 replies
Pdawg

Micro-optimizing a Z80 emulators' pipeline. **Unsafe code**

so, i'm writing an emulator, and i'm trying to squeeze as much perf as i can out of hot paths. this seemingly simple fetch operation consumes about a third of the CPU time:
private byte Fetch()
{
    return _memory.Read(Registers.PC++);
}
private byte Fetch()
{
    return _memory.Read(Registers.PC++);
}


my memory class looks like this:
private GCHandle _memHandle;
private byte* pMem;
private byte[] _memory;

public MainMemory(int size) 
{
    // pin array and get GC ptr. omitted for brevity.
    pMem = (byte*)_memHandle.AddrOfPinnedObject();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte Read(ushort address) => pMem[address];
private GCHandle _memHandle;
private byte* pMem;
private byte[] _memory;

public MainMemory(int size) 
{
    // pin array and get GC ptr. omitted for brevity.
    pMem = (byte*)_memHandle.AddrOfPinnedObject();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte Read(ushort address) => pMem[address];

i know, it's a bit messy, and it's not really safe either, but boy does it give some perf gains. it's worth it. also, the array wont change size so it should be alright.

my registers are in a similar situation. the register set is actually an array of bytes that are accessed using constant indexers into said array, like this:
private GCHandle _regSetHandle;
private byte* pRegSet;
public byte[] RegisterSet;
public ProcessorRegisters()
{
    RegisterSet = new byte[26];
    _regSetHandle = GCHandle.Alloc(RegisterSet, GCHandleType.Pinned);
    pRegSet = (byte*)_regSetHandle.AddrOfPinnedObject();
}
// example
byte regA = pRegSet[Registers.A]; // A is an indexer into the array; i tried to follow the Z80's convention, so A = 7
private GCHandle _regSetHandle;
private byte* pRegSet;
public byte[] RegisterSet;
public ProcessorRegisters()
{
    RegisterSet = new byte[26];
    _regSetHandle = GCHandle.Alloc(RegisterSet, GCHandleType.Pinned);
    pRegSet = (byte*)_regSetHandle.AddrOfPinnedObject();
}
// example
byte regA = pRegSet[Registers.A]; // A is an indexer into the array; i tried to follow the Z80's convention, so A = 7


but, this is a Z80, meaning it also has 16-bit register pairs. this is important, because you can either access it as its high and low parts, or its entire pair, meaning that the exposed pairs depend on this same array, so i implemented them using properties
public ushort PC
{
    get => (ushort)((pRegSet[PCi] << 8) | pRegSet[PCiL]);
    set
    {
        pRegSet[PCi] = (byte)(value >> 8);
        pRegSet[PCiL] = (byte)value;
    }
}
public ushort PC
{
    get => (ushort)((pRegSet[PCi] << 8) | pRegSet[PCiL]);
    set
    {
        pRegSet[PCi] = (byte)(value >> 8);
        pRegSet[PCiL] = (byte)value;
    }
}


with all of this in mind, how can i make that fetch instruction faster and use less CPU time?
C# banner
C#Join
We are a programming server aimed at coders discussing everything related to C# (CSharp) and .NET.
61,871Members
Resources
Was this page helpful?

Similar Threads

Recent Announcements
Next page

Similar Threads

Optimizing the pipeline on my Z80 emulator
C#CC# / help
2y ago
✅ Unsafe code
C#CC# / help
9mo ago
❔ Optimizing code
C#CC# / help
3y ago
✅ unsafe code problems
C#CC# / help
11mo ago