C
C#6mo ago
Pdawg

Which is faster - a type LUT or a switch?

Hello again, C# Discord! I've got a question about performance, which is absolutely critical since I'm making an emulator. When decoding Z80 instructions that involve registers, there's a clever little hack to pull out the "operating register", or the one that the opcode is referencing. I get that a simple switch could work, but it creates a lot of repeating code. For example:
switch (opcode)
{
case 0x01:
Registers.BC = FetchImmWord();
break;
case 0x11:
Registers.DE = FetchImmWord();
break;
case 0x21:
Registers.HL = FetchImmWord();
break;

}
switch (opcode)
{
case 0x01:
Registers.BC = FetchImmWord();
break;
case 0x11:
Registers.DE = FetchImmWord();
break;
case 0x21:
Registers.HL = FetchImmWord();
break;

}
Notice how the only thing changing is the register it's referencing. So, I want to use the hack to extract the operating register out of the current opcode and then set the corresponding register accordingly, so I can combine all 3 cases (0x01, 0x11, and 0x21) into one that uses the operating register value. I need to know how I should implement this, and I've landed on two options - a switch statement (switch (operatingRegister) ... etc), or, a LUT using a Dictionary<byte, Action>. Which one do you think would perform better? There are 26 possible values that the operating register can be, if that helps you all decide. Thanks a lot!
No description
47 Replies
sibber
sibber6mo ago
for something like this id say benchmark and see
Pdawg
PdawgOP6mo ago
how would you recommend I do a proper benchmark? just stopwatch a loop that gets and sets all registers?
sibber
sibber6mo ago
GitHub
GitHub - dotnet/BenchmarkDotNet: Powerful .NET library for benchmar...
Powerful .NET library for benchmarking. Contribute to dotnet/BenchmarkDotNet development by creating an account on GitHub.
Pdawg
PdawgOP6mo ago
awesome - thanks!
sibber
sibber6mo ago
also https://sharplab.io/ is really useful if you want to see what something get lowered/jitted to
SharpLab
C#/VB/F# compiler playground.
canton7
canton76mo ago
A switch statement will normally outperform a dictionary. The compiler has all sorts of tricks it can use, including generating a lookup To be honest, having those 3 separate cases is probably going to be quicker than trying to be clever. In both cars the compiler had to find right case for the opcode, but in your second approach it's then got to do a bunch of bitwise arithmetic and maybe some branching to find the right register on top of that
Pdawg
PdawgOP6mo ago
there’s a lotttt of cases where this register lookup is used so I feel like it’ll make it readable and make my life easier developing it
canton7
canton76mo ago
How many times are you repeating switch (opcode)? I can't imagine you need to copy/paste that a lot - and if you do, can't you call a helper function which has the switch?
Pdawg
PdawgOP6mo ago
I’ve seen someone else do this same hack, but their registers are just represented by an array of bytes so they can just take the output of this operating register operation and index into the array accordingly. I really don’t to make my registers be an array of bytes though cause it’s not readable and its hard to remember what goes where switch (opcode) is executed in a loop until the PC hits the end of memory or if the processor is halted I can do a function call but it doesn’t eliminate the issue I have, which is repeated code I want this emulator to perform well but be very readable and easy to understand
canton7
canton76mo ago
I still don't understand why it's repeated. If it's called in a loop, that doesn't mean the code is repeated...
Pdawg
PdawgOP6mo ago
I mean the code inside of the 3 cases it’s the same code but it’s referencing a different type. And again, this “hack” is useful in a lot of scenarios so it’ll make everything a lot cleaner and easier to write
canton7
canton76mo ago
I still don't understand why you can't put the switch (opcode) (first code sample in your question) into a helper function, if needed (bear in mind I don't know the Z80 instruction set) (and have no idea what the "byte array hack" is)
Pdawg
PdawgOP6mo ago
someone else made a z80 emulator, whose register set is defined as an array of bytes. this allows them to index into it given the operatingRegister value directly and get/set that part accordingly. my register set is defined as separate types
No description
Pdawg
PdawgOP6mo ago
im against this idea of using a byte array as the register set because it's unreadable and makes it a pain for anyone who doesn't understand the Z80
canton7
canton76mo ago
I don't see a byte array in that code, or how that relates to opcode decoding
Pdawg
PdawgOP6mo ago
the screenshot is my register set
Pdawg
PdawgOP6mo ago
see, this is how it's currently done
No description
Pdawg
PdawgOP6mo ago
but this code is all repetitive there's no technical reason why this wouldn't work, but it's just not ideal and i'd like to use the operating register value
canton7
canton76mo ago
How many cases are there? Why are you copy/pasting that switch statement around?
Pdawg
PdawgOP6mo ago
these are the cases for the ld dd, nn opcodes
No description
Pdawg
PdawgOP6mo ago
every single instruction will be parsed under this root switch so i want to keep it as small as possible and moving each little set of instructions out to helper functions doesnt solve the problem, it just moves the repeated code out to another spot
Pdawg
PdawgOP6mo ago
No description
canton7
canton76mo ago
Ah so there are many more cases than the 3 you've referred above?
Pdawg
PdawgOP6mo ago
yes this is just an example this will have to be done all over the place if i don't implement this clever little lookup
canton7
canton76mo ago
Right! You didn't say that. You said you didn't want to keep repeating the three cases you already had
Pdawg
PdawgOP6mo ago
lol, sorry. but this repetitiveness happens all over the place because im lacking an implementation for the operating register lookup
canton7
canton76mo ago
Or not? Why "all over the place"? Why does anything except the opcode decoder care about this?
Pdawg
PdawgOP6mo ago
nothing else does this massive switch doesnt effect anything from a code standpoint this is all semantic it makes the code a pain to read
canton7
canton76mo ago
So you're not repeating them "all over the place"? Just in that one switch statement?
Pdawg
PdawgOP6mo ago
again, i want this to be as simple, clean, and readable as possible correct repeating code is still bad regardless
canton7
canton76mo ago
Right OK
Pdawg
PdawgOP6mo ago
im personally not a fan, like at all
canton7
canton76mo ago
Well, ish. Often verbose code is more understandable more quickly, as you don't need to learn how all of the abstraction work
Pdawg
PdawgOP6mo ago
sort of, yeah, but thisll end up being a 4000 line file if everything has to be repeated lol
canton7
canton76mo ago
Also your debug code doesn't need to be on the fast path, so you can potentially reduce the body if each case down to 1 line
Pdawg
PdawgOP6mo ago
this is a library so i cant use preprocessor directives. the debug stuff has to be done at runtime to enable/disable logging depending on the mode of the processor
canton7
canton76mo ago
If you want to move the complexity out of the switch, you can make a register an object (rather than a property), and have a method which returns the register for a particular opcode
Pdawg
PdawgOP6mo ago
it has another mode where logging is disabled for performance but thats besides the point
canton7
canton76mo ago
You misunderstand. I mean you can use slower constructs for logging, such as dictionary lookups etc
Pdawg
PdawgOP6mo ago
wdym? the logging happens right when an instruction is decoded so it kind of has to be inlined with the rest of the actual instruction behavior
canton7
canton76mo ago
But anyway. Make a register an object. Write a method which gets the register for a given "byte array offset"
Pdawg
PdawgOP6mo ago
thats kinda what i was thinking of doing but wouldnt it require reflection to pull that off to return a type reflection is slow
canton7
canton76mo ago
For example, if a register is an object, it can return its name, and you can use that the construct the log message No?
Pdawg
PdawgOP6mo ago
yeah, but im not worried about the logging code rn im more worried about the actual setting of the right register whether it be BC, DE, or HL
canton7
canton76mo ago
Stick your register objects in an array, do a O(1) lookup based on the same index as the byte array hack uses
Pdawg
PdawgOP6mo ago
ahhhh wait youre on to something yeah okay i get what youre saying thats actually a great idea, thanks!
canton7
canton76mo ago
Your register objects can just be references back to your existing register bank storage thingy, if you still want to keep all of the register values in one place
Want results from more Discord servers?
Add your server