From f36973d9961a3dd583dd4896a78a55895199aeb2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 9 Feb 2025 19:10:57 +0100 Subject: [PATCH] Optimize Cartridge::Mbc1 Closes: https://github.com/sacckey/rubyboy/pull/23 Having to call a proc for every memory access isn't ideal, that quite a bit of overhead. If instead we use a `case` statement with only immediate values and a few bitwise operations we can let YJIT generate very efficient code. two runs on main with ruby 3.4.1 / YJIT: ``` Ruby: 3.4.1 YJIT: true 1: 4.051815 sec 2: 3.989457 sec 3: 3.963555 sec FPS: 374.849216902501 Ruby: 3.4.1 YJIT: true 1: 3.872855 sec 2: 3.943231 sec 3: 4.024268 sec FPS: 380.05620440064547 ``` On this branch: ``` Ruby: 3.4.1 YJIT: true 1: 3.625805 sec 2: 3.615032 sec 3: 3.572852 sec FPS: 416.1392102177157 Ruby: 3.4.1 YJIT: true 1: 3.543953 sec 2: 3.520738 sec 3: 3.506004 sec FPS: 425.70521616601366 ``` --- lib/rubyboy/cartridge/mbc1.rb | 86 ++++++++++++++--------------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/lib/rubyboy/cartridge/mbc1.rb b/lib/rubyboy/cartridge/mbc1.rb index 833eeee..1f1a474 100644 --- a/lib/rubyboy/cartridge/mbc1.rb +++ b/lib/rubyboy/cartridge/mbc1.rb @@ -10,65 +10,47 @@ def initialize(rom, ram) @ram_bank = 0 @ram_enable = false @ram_banking_mode = false - - @read_methods = Array.new(0xc000) - @write_methods = Array.new(0xc000) - - set_methods end - def set_methods - 0xc000.times do |addr| - @read_methods[addr] = - case addr - when 0x0000..0x3fff then -> { @rom.data[addr] } - when 0x4000..0x7fff then -> { @rom.data[addr + (@rom_bank - 1) * 0x4000] } - when 0xa000..0xbfff - lambda do - if @ram_enable - if @ram_banking_mode - @ram.eram[addr - 0xa000 + @ram_bank * 0x800] - else - @ram.eram[addr - 0xa000] - end - else - 0xff - end - end - end - end - - 0xc000.times do |addr| - @write_methods[addr] = - case addr - when 0x0000..0x1fff then ->(value) { @ram_enable = value & 0x0f == 0x0a } - when 0x2000..0x3fff - lambda do |value| - @rom_bank = value & 0x1f - @rom_bank = 1 if @rom_bank == 0 - end - when 0x4000..0x5fff then ->(value) { @ram_bank = value & 0x03 } - when 0x6000..0x7fff then ->(value) { @ram_banking_mode = value & 0x01 == 0x01 } - when 0xa000..0xbfff - lambda do |value| - if @ram_enable - if @ram_banking_mode - @ram.eram[addr - 0xa000 + @ram_bank * 0x800] = value - else - @ram.eram[addr - 0xa000] = value - end - end - end + def read_byte(addr) + case (addr >> 12) + when 0x0, 0x1, 0x2, 0x3 + @rom.data[addr] + when 0x4, 0x5, 0x6, 0x7 + @rom.data[addr + ((@rom_bank - 1) << 14)] + when 0xa, 0xb + if @ram_enable + if @ram_banking_mode + @ram.eram[addr - 0xa000 + (@ram_bank << 11)] + else + @ram.eram[addr - 0xa000] end + else + 0xff + end end end - def read_byte(addr) - @read_methods[addr].call - end - def write_byte(addr, value) - @write_methods[addr].call(value) + case addr >> 12 + when 0x0, 0x1 + @ram_enable = value & 0x0f == 0x0a + when 0x2, 0x3 + @rom_bank = value & 0x1f + @rom_bank = 1 if @rom_bank == 0 + when 0x4, 0x5 + @ram_bank = value & 0x03 + when 0x6, 0x7 + @ram_banking_mode = value & 0x01 == 0x01 + when 0xa, 0xb + if @ram_enable + if @ram_banking_mode + @ram.eram[addr - 0xa000 + (@ram_bank << 11)] = value + else + @ram.eram[addr - 0xa000] = value + end + end + end end end end