ANNOUNCE: OmegaGB, Haskell Game Boy Emulator
OmegaGB is an emulator for the Nintendo Game Boy, written in pure haskell. It's in a very early state, and only barely shows the title screen of a few games. It uses gtk2hs for the user interface, but there is also a version that doesn't require gtk2hs and uses ascii art. The main problem I am having is getting decent performance. I have set up a darcs repository in the hopes of receiving advice or patches to improve performance. You can find more information about the program at the website: http://www.mutantlemon.com/omegagb/ The program works by maintaining a state of all of the gameboy hardware: cpu registers, memory, and internal timers for various interrupts and other things. A function is used to update the state: updateMachineDisplayFrame :: JoypadKeyStates -> ((RegisterStates, Memory), IrqStates) -> (Display, ((RegisterStates, Memory), IrqStates)) This approach does not give enough performance, and the emulator is not able to run in real time. I have experimented with replacing most of the state with mutable IORefs and an IOUArray for the memory. This gave approximately double the performance, but that is still only 10% of real time speed on my computer. And all this is still without emulating most of the GB graphics hardware, which will require quite a bit more computations. I originally started this project to explore whether haskell is really a capable language in the domain of performance critical applications. I think that with the right optimizations, OmegaGB will be able to do real time emulation. Unfortunately, I am not experienced enough to know what kind of optimizations to use. This is why I am calling out for help. Thanks, Bit Connor PS: Here is some of the profiling output from a typical run: total time = 174.10 secs (3482 ticks @ 50 ms) total alloc = 31,338,925,440 bytes (excludes profiling overheads) COST CENTRE MODULE %time %alloc renderScanLine Machine 28.6 17.9 machineCpuExecute' Machine 12.6 11.4 tickHBlank Machine 9.1 21.1 tickHBlankMode0 Machine 6.3 13.7 executeInstruction Cpu 5.6 1.6 test02 GuiTest02 5.0 1.4 tickDIV Machine 4.7 13.1 irqUpdate Machine 3.4 0.8 transformIrq Machine 3.3 3.2 tickHBlankMode3 Machine 3.1 6.9 mcti Cpu 2.2 0.5 setRegState Machine 1.9 1.7 setReg2State Machine 1.7 1.8 doFromTo GuiDrawUtil 1.6 1.5 updateMachine Machine 1.4 0.5 updateMachineDisplayFrame Machine 1.3 0.5 writeFlags CpuExecution 1.2 0.3 fetchInstruction Machine 1.1 0.5
i've seen no replies to this so far, so just a few brief comments: first, many readers like myself may have skipped this announcement because they aren't currently interested in the specific application. what the announcement didn't say is that there is a small development blog for the project, browsing which might be of interest to haskell hackers and implementers alike. we get so few experience reports, and this project includes modeling a code- interpreting virtual machine with a gui, and providing a gui to the model itself, so it might appeal to non-gamers as well (and it is always nice to hear about haskell being applied to fun projects;-): http://www.mutantlemon.com/omegagb/devlog/ second, calling for help with optimisation is okay, but i doubt that many readers will download a full application to search for possible problems in it (there is always the chance of one of the profiling/debugging projects looking for examples like this, but i don't know whether there are any such projects active right now). you did provide a useful overview of your approach, your problems, and the techniques you've tried so far to resolve them, but that kind of overview might be too abstract to provide concrete tips with any certainty about their usefulness. for example, looking at your overall representation of machine instructions and machine state suggests possible problems with space leaks. the tuples are non-strict, so the updates caused by instructions could possibly accumulate until another instruction needs to inspect the results of those updates.
updateMachineDisplayFrame :: JoypadKeyStates -> ((RegisterStates, Memory), IrqStates) -> (Display, ((RegisterStates, Memory), IrqStates))
but if you actually display a representation of the machine state at each step, that alone should force all pending instructions, so perhaps this isn't an issue in the real program. then again, your experience with unboxed arrays suggests that perhaps this is an issue after all. and even if there are no runaway space leaks, repeatedly delaying operations only to have them forced a few moments later might have a negative impact on performance.
I originally started this project to explore whether haskell is really a capable language in the domain of performance critical applications. I think that with the right optimizations, OmegaGB will be able to do real time emulation. Unfortunately, I am not experienced enough to know what kind of optimizations to use. This is why I am calling out for help.
skimming through your blog, two things come to mind: - the virtual machine for such a device is unlikely to do any operation unless the results are needed, so you probably want your state representations to be strict in all components. depending on how well the strictness analyser fares with your code, that might save some intermediate thunk building - instead of interpreting the machine instructions in executeInstruction, you could implement them directly in your execution monad, eliminating one level of interpretation at each step (ie, there'd be a push :: srtype -> ExecutionAST ()); if you can also switch from lists of instructions to monadic sequences of instructions, there might be further gains from asking ghc to inline the instructions skipping explicit representations, as per the second suggestions, might make extracting debug information slightly more difficult, so you may want to keep the explicit version around, or integrate debugging into your execution monad (just something to keep in mind). oh, lest i forget, there's also: http://www.haskell.org/haskellwiki/Performance just to get some discussion going;-) claus
Thanks for the reply. I think that you are right that laziness and too much abstraction are the main reasons for low performance. I've actually received some patches from a Mr. Lemmih, who converted some of the code to use the ST monad, and managed to get results with very good performance. I am confident that with work, it should be possible to complete the omegagb project and have a working game boy emulator written in pure haskell. On the other hand, I would like to mention that I have reimplemented most of the omegagb code in C, just for fun. Obviously it runs much faster then the haskell version, by a factor of about 20. More interestingly though, is that the C version isn't longer, it's not more verbose, and it's not any harder to read then the haskell version. It doesn't have any of the monad goodness of the haskell version, and it would probably be harder to proove things about the code, but there is something to be said for lean and mean C code. The C language is a very good fit for this problem domain. If I had to do a real world project for a similar problem, I'm not sure that haskell would have that much of an advantage over C, even if performance wasn't an issue. Then again, I imagine that If I were to try to attempt to make an emulator for a computer more complicated then the game boy, then a hybrid C/Haskell approach might be very attractive. I'm putting OmegaGB on the back burner for now and am moving on to other projects. Peace, Bit
participants (2)
-
Bit Connor -
Claus Reinke