4 ms·
Great work and great articles. > Yet another patch in Mame’s PR was to use one of my favorite performance tricks, what’s called a “lookup table”. One thing st
by Lammy 2y ago
Great work and great articles.
> Yet another patch in Mame’s PR was to use one of my favorite performance tricks, what’s called a “lookup table”.
One thing stood out to me here as a fellow lookup-table-liker that I would like to mention even though it is probably not relevant to a generic JSON generator/parser which has to handle arbitrary String Encodings.
The example optimized code uses `String#each_char` which incurs an extra object allocation for each iteration compared to `String#each_codepoint` which works with Immediates. If you are parsing/generating something where the Encoding is guaranteed, defining the LUT in terms of codepoints saves a bunch of GC pressure from the throwaway single-character String objects which have to be collected. One of the pieces of example code even uses `#each_char` and then compares its `#ord`, so it's already halfway there.
I don't feel like compiling 3.4-master to test it too, but I just verified this in Ruby 3.3.6:
irb(main):001> "あああ".-@.each_char { p _1.object_id }
4440
4460
4480
=> "あああ"
irb(main):002> "あああ".-@.each_codepoint { p _1.object_id }
24709
24709
24709
=> "あああ"
irb(main):003> RUBY_VERSION
=> "3.3.6"
Apologies for linking my own hobby codebase, but here are two examples of my own simple LUT parsers/generators where I achieved even more performance by collecting the codepoints and then turning it into a `String` in a single shot with `Array#pack`:
- One from my filetype-guessing library that turns Media Type strings into key `Structs` both when parsing the shared-mime-info Type definitions and when taking user input to get the Type object for something like `image/jpeg`: https://github.com/okeeblow/DistorteD/blob/fbb987428ed14d710461ed63bb737475ab09fcd8/CHECKING%20YOU%20OUT/lib/checking-you-out/auslandsgesprach.rb#L49-L247 https://github.com/okeeblow/DistorteD/blob/fbb987428ed14d710... (Comment contains some before-and-after allocation comparisons)
- One from my support library that turns POSIX Globs into Ruby `Regexp` objects like Python's stdlib `fnmatch.translate`: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSATION/XROSS%20THE%20XOUL/lib/xross-the-xoul/posix/glob.rb https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
Disclaimer: I haven't benchmarked this with YJIT which might render my entire experience invalid :)
- byroot 2y ago> The example optimized code uses `String#each_char` which incurs an extra object allocation for each iteration You're right. To be closer to the real C code, I should have used `each_byte`, given the C code works with bytes and not codepoints nor characters. But this is mostly meant as pseudo-code to convey the general idea, not as actually efficient code, so not a big deal.