GResource is a tool that compiles files (external to the source code) into your app’s executable. By doing so it significantly cuts down on kernel calls, merging it all into the same mmap operations performed by exec(). Swapping it in from the filesystem as needed.
It consists of:
- A tool to compile a simple XML file into a binary database, a C file wrapping it, and a compiled module.
- An API to register and use those binary database.
- An underlying in-memory file system.
That commandline build tool starts by using GMarkup to parse the XML, and for each file it runs the specified preprocessing tools (plus compression) and reads it into a hashmap. Which is transliterated over into a “GVDB” database which is written into a temp file.
Then it might generate some dependencies build system metadata, before reading those bytes back in to phrase as a C array and wrap in a few functions. Possibly using GConstructor/compiler-specific macros to automatically run them.
Once I have linked in those libraries I can call an API to try loading a named resource from each registered (in a doubly-linked list) GResource VDBE database. Those APIs will then wrap the results in a GBytes or GMemoryInputStream object after applying the optional decompression.
For Odysseus’s templating language I use GBytes, and with that I never have to copy any of the source code to parse it!
Then there’s a concept of of “overlays” which I won’t cover, but are useful during development.
That underlying GVDB database engine (which is also for settings) is essentially a binary data file (again parsed via GBytes) storing a tree of hashmaps. This tree is simultaneously flattened (for file lookups) and linked (for directory listing).
There’s heavy use of pointers as offsets from the file’s start to reduce the need for parsing, and the hashtables of a (partially implemented) bloom filter.
The values are GValue-encoded, but GResource reads it as a sized and flagged array.
One simple GLib utility Odysseus templating language, and GCR’s ASN1 parser, uses to aid parsing is GBytes. This wraps an immutable bytearray with a length and reference counting, calling a given callback when that refcount reaches 0.
The benefit is that it makes taking a slice extremely cheap, by creating a new GBytes which dereferences it’s parent.
It only costs some addition/subtraction and a tiny fixed-size allocation, which GLib’s GSlice allocator ensures is fast.
Some additional notes here:
- The GSlice allocator makes it fast to reuse recently freed fixed-sized objects, and will allocate a “page” full of them at a time if no others are available.
- Vala’s arrays also track their length rather than use a nil terminator, but without the reference counter the memory gets copied around between methods. This has been a bottleneck for me.
- I use my own wrapper around GBytes so they work better with LibGee and to test the performance fix I sent upstream.
- It makes it cheap for my templating language to be parsed in layers.