Comment by jiggawatts

10 hours ago

I wish more programming languages implemented record types as seen in databases, where dynamically sized fields are packed into a contiguous area of memory.

The CloudFlare manually implemented a clumsy version of this.

Wouldn’t it be nice for the compiler to manage this for you in the same way that your database engine does when it saves a “row”?

> dynamically sized fields are packed into a contiguous area of memory

Are you able to explain this? Do you mean an N sized array where each entry is either a value or a pointer to a value where the 'pointed-to' values are after the end of the array?

I'm trying to underatnd how you'd do this without having to parse M-1 elements to get the Mth entry if you did a [{size0, value0}, ....., {sizeN, valueN}] arrangement

  • I think they mean the cache entry is a collection of dynamically sized fields. It would be nicer to store that as a single contiguous allocation, rather than a bunch of pointers to individually allocated dynamically sized items. At least in this case, it might.

    In a row oriented database, you get a contiguous spot for the whole row even when there are multiple variable width fields.

  • There are various ways of implementing this, someone from a C programming background mentioned on option where the heap-allocated record objects aren't fixed size structs, but instead the allocated space is dynamically sized and the struct is just a prefix.

    So logically you'd have the equivalent of:

        struct FooRecord {
            int fixed_sized_field;
            char some_other_field;
            string first;
            string last;
            string title;
        }
    

    Physically the compiler would generate something like:

        struct FooRecord {
            long __length__;
            int fixed_sized_field;
            char some_other_field;
            char* first;
            char* last;
            char* title;
        }
    

    Where 'first', 'last', and 'title' are sequentially stored after the struct in the heap memory.

    There are variants of the above, of course. Instead of pointers the compiler could use lengths, offsets, or a pointer to the end of the variable length field -- this works because the beginning of the first field is at a fixed offset, and then pairs of pointers delimit the rest.

    You can rely on the heap allocator to track the "__length__" instead, or you can encode it into the record explicitly to make "dynamic sized copies" simple.

    Windows APIs generally work this way! You create a buffer, put a length in the first field, and then the API call writes a fixed-sized prefix followed by the dynamic-sized fields into the buffer. The 'length' is replaced too, so you know how many bytes to copy out without having to understand the structure.

    Database engines go one step further and pack multiple "records" into a single "row". They typically store the fields "packed" at the start of the row with 16-bit length or offset markers at the end for the various dynamic sizes.

    Something like:

        fixed_sized_field // Row #0
        some_other_field
        first
        last
        title
        fixed_sized_field // Row #1
        some_other_field
        first
        last
        title
        ... empty space ...
        next_offset      // always populated
        row#1_title_offset
        row#1_last_offset
        row#1_first_offset
        row#1_offset
        row#0_title_offset
        row#0_last_offset
        row#0_first_offset
        row#0_offset  // typically the constant zero
    
    

    The idea here is that every length is the difference between pairs of sequential offsets. I.e. row#1_title has length (next_offset-row#1_title_offset).