5 ms·
https://github.com/shish/rosettaboy https://github.com/shish/rosettaboy The same gameboy emulator rewritten in C++, Go, Nim, PHP, Cython, Python, Rust, and Zig
by Shish2k 4y ago
https://github.com/shish/rosettaboy https://github.com/shish/rosettaboy
The same gameboy emulator rewritten in C++, Go, Nim, PHP, Cython, Python, Rust, and Zig (and WIP typescript); mostly to teach myself the languages and to compare and contrast their idioms.
Also, when taken with a very large grain of salt, usable as a language benchmark (As with all benchmarks, there are lots of caveats - but as far as I’m aware this is unique in being “the same code in multiple languages” and “several thousand lines of code”):
$ ./utils/bench.py
rs / lto : Emulated 15763 frames in 10.00s (1576fps)
cpp / lto : Emulated 14737 frames in 10.00s (1474fps)
rs / release: Emulated 13183 frames in 10.00s (1318fps)
cpp / release: Emulated 12966 frames in 10.00s (1297fps)
zig / release: Emulated 8792 frames in 10.00s (879fps)
nim / speed : Emulated 8127 frames in 10.00s (812fps)
nim / release: Emulated 6161 frames in 10.00s (616fps)
cpp / debug : Emulated 5693 frames in 10.00s (569fps)
go / release: Emulated 5040 frames in 10.00s (504fps)
pxd / release: Emulated 3792 frames in 10.00s (379fps)
nim / debug : Emulated 1968 frames in 10.00s (196fps)
rs / debug : Emulated 1676 frames in 10.00s (168fps)
py / mypyc : Emulated 887 frames in 10.01s (89fps)
php / opcache: Emulated 613 frames in 10.01s (61fps)
php / release: Emulated 255 frames in 10.01s (25fps)
py / release: Emulated 101 frames in 10.06s (10fps)
zig / safe : Emulated 40 frames in 10.00s (4fps)
- winrid 4y agoWhat's your favorite, and what do you think about Nim? EDIT nvm, found: https://github.com/shish/rosettaboy/tree/master/nim https://github.com/shish/rosettaboy/tree/master/nim
- dom96 4y agoReally cool! What were your impressions of all the languages you've implemented this in?
- Shish2k 4y agoFor a few of the languages I’ve added a “thoughts on this language” section in the language folder’s README, but I only started doing that after a few implementations were already finished, so it’s a bit inconsistent - maybe it’d be worth making it consistent, and writing up as a single easy-to-read webpage, and submitting that to HN :P
- compumike 4y agoVery cool! Are you open to other people contributing PRs to add other languages, or is this a learning project and prefer to do it yourself? (I'm considering adding Crystal https://crystal-lang.org/ https://crystal-lang.org/)
- Shish2k 4y agoMore languages would totally be welcome, so long as they’re following the same general source code layout so that it’s easy to see how different parts map to each other :)
- detrites 4y agoIs the github language summary indicative of relative LoC between implementations, or are there other factors? C++ 24.9% Python 19.5% Rust 12.5% PHP 9.5% Zig 9.4% Nim 8.0% Other 16.2% And very interesting! Would love to read (a post?) on how you saw the language differences.
- shhsshs 4y agoYou would probably need a much larger sample set to give a "true" indication of relative LoC between different languages. Right now it's probably a good indicator of relative LoC between languages when writing a Game Boy emulator.
- vageli 4y agoAt least for the python percentage, it is skewed as both `py` and `pxd` contain python code.
- Shish2k 4y agoC++ and Rust contain (incomplete attempts at) emulating the sound chip - the other languages just have an empty stub (I want to get at least one implementation working properly before using it as a reference to write the others - interestingly both of those implementations, despite being written based on the same specs, sound wrong for different reasons…) Also I wonder how it counts “lines” - if it is literal newline characters, then probably the biggest factor there is that some languages insist on having a newline after each case in a switch statement and some don’t — so in the CPU implementation some languages are like case 0x42: self.PC = self.A; break; and others are like case 0x42: self.PC = self.A; break; (which adds up quite a bit when multiplied by 500 CPU instructions) If somebody wanted to go to the effort of counting logical statements per language, I’d be interested in seeing what that looks like :) It’s also probably worth comparing module-by-module — like for all languages, the CPU implementation does pretty much exactly the same thing in exactly the same way; but for command line argument parsing, every language is very different.
- Shish2k 4y agoTo speak entirely subjectively, nearly all of the languages felt like I was writing approximately the same amount of code - the only exceptions being C++ (where I needed to keep switching between .cpp and .h files and manually keeping them in sync, which felt like maybe 20% mental overhead), and Go (writing `if err != nil { return err }` after every line of code became very annoying very quickly, feeling like I was writing twice as many lines of code - and I still managed to forget to do error handling in a couple of places where it actually mattered, since it only forces you to handle `err` if you’re also handling some other return value...)
- brailsafe 4y agoNeat. I haven't done a deel dive on any kind of technical project in a while, where did you learn amount programming for emulation initially?
- Shish2k 4y agoInitially I was using a fairly-well-known PDF which explained the gameboy hardware with 90% accuracy, and focussed on just getting the CPU working to a point where it could print “hello world” to the link cable port. After that, I discovered that some people had written test ROMs - given only the most basic CPU instructions being implemented in a mostly-correct-ish way, these ROMs exercise the more exotic instructions, and edge-cases of the common instructions, and print out an error code if the emulated CPU gives results that differ from the physical CPU. (In this case the error code is just “this instruction is implemented wrong”, without saying what’s wrong about it - but with the gameboy having a fairly simple CPU where the vast majority of instructions map to a single statement in a high-level language, it’s good enough for most cases) Third, I discovered https://gbdev.io/ https://gbdev.io/ which is an open source documentation project which has been bug-fixed and kept up to date and is generally much higher quality than the PDF I started from. Things would have gone much faster if I’d started here :) In all cases the architecture of my emulator is very basic - no JIT, no worries about sub-CPU-cycle timing being accurate, etc[0]; just a loop which reads an instruction from memory, has a big `switch` statement to decide how to act upon that instruction, and then goes back to read the next instruction. [0] I think the only place where I’ve allowed a bit of complexity in rather than sticking with the simplest implementation is the GPU - the simplest approach is to render all of the GPU memory to the screen once per frame; the slightly more accurate approach (which I do) is to render one row of pixels at a time (so that the CPU can tweak settings mid-frame to achieve parallax effects); the real hardware is quite a bit more complicated, but the medium-complexity approach works perfectly for nearly all the games I care about, and only has minor glitches for cases where it’s wrong.
- KerrAvon 4y agoInteresting! Zig release perf is respectable, but slower than Python for debug clearly leaves some room for improvement.
- Shish2k 4y agoYeah, I haven’t dug into what’s happening there - with the previous version of the compiler debug perf seemed to be nearly as fast as release perf :S