News for hackers. Stuff that matters.
Re:A Database Without Dynamic Memory Allocation by aidenn0 on Thursday October 13, 2022 @12:31PM (#33193270)
I've been an embedded software developer for almost 2 decades, and this style used to be near universal in embedded, but has become less so, much to my dismay. I even saw a project not too long ago where a large C++ STL List (i.e. a linked list) was created at startup, then later freed piecemeal, causing so much heap fragmentation on the poor embedded system malloc implementation.
Re:A Database Without Dynamic Memory Allocation by LunaSea on Thursday October 13, 2022 @12:38PM (#33193376)
Would you have some links to posts that talk about this style of memory management and allocation?
Re:A Database Without Dynamic Memory Allocation by malkia on Thursday October 13, 2022 @12:46PM (#33193484)
Game Development still revolves around this. For example when loading a level, it used to be you have huge arena, you load your "serialized" map at one end (say top), and your other chunks at the bottom - you have some "flexibility" to pop from top or bottom, so early on you arrange for that. Alternatively you split your memory per sized big chunks, and stream in out these (works well for textures, and primed sounds - e.g. the first 64kb or 128kb of a sound file).
Re:A Database Without Dynamic Memory Allocation by aidenn0 on Thursday October 13, 2022 @01:40PM (#33194183)
I don't have some links, but it's more straightforward than you might think (and TFA covers some of it).

1. Bound every whatever that requires memory to some value N

2. For all whatevers that can have a lifetime for your entire program, do that

3. For any whatevers that can't have a lifetime for your entire program (e.g. for a database server like TFA, a connection object would qualify), make a table of size N

4. When you need a whatever (again using a connection object as an example, when listen() returns) grap an unused entry from the table. If there is no unused entry do the appropriate thing (for a connection, it could be sending an error back to the client, just resetting the TCP connection, blocking, or logging the error and restarting the whole server[1]).

5. When you are done with a whatever (e.g. the connection is closed) return it to the table.

Those are the basics; there are some details that matter for doing this well e.g.:

- You probably don't want to do a linear-probe for a free item in the table if N is larger than about

- Don't forget that the call-stack is a form of dynamic allocation, so ensure that all recursion is bounded or you still can OOM in the form of a stack overflow.

I should also note, that for certain values of N, doing things this way is normal:

- When N=1 then it's just a global (or C "static" local variable).

- When N=(number of threads) then it's just a thread-local variable

1: The last one sounds extreme, but for a well understood system, too many connections could mean something has gone terribly wrong; e.g. if you have X client instances running for the database, using connection pools of size Y and you can have 2XY live connections in your database, exceeding N=2XY is a Bad Thing that recovering from might not be possible

Re:A Database Without Dynamic Memory Allocation by hobs on Thursday October 13, 2022 @01:54PM (#33194314)
Missing the end of "- You probably don't want to do a linear-probe for a free item in the table if N is larger than about"
Re:A Database Without Dynamic Memory Allocation by ericbarrett on Thursday October 13, 2022 @02:10PM (#33194466)
I was curious about that too :) My guess is 16 items (edit: OP says 12) but I'm not an embedded developer, and I'm sure it's hardware-specific.

The approach I've seen for larger fixed-sized tables is a free list: when the table is allocated, also initialize an array of the slot numbers, with an associated length index. To allocate, pop the last slot # off the array and decrement the length index. To free, push the slot # onto the end of the array and increment the length index. Checking for a free slot is then just testing for length index > 0.

Re:A Database Without Dynamic Memory Allocation by aidenn0 on Thursday October 13, 2022 @03:22PM (#33195344)
Yeah, a stack of free indexes is good. In the rare case of "lots of indexes, but little (extra) memory" a bitmap can be good because it's 1 bit per index rather than ⌈log2(N)⌉ bits per index and has a constant factor speed up equal to the word-size of the architecture.
Re:A Database Without Dynamic Memory Allocation by aidenn0 on Thursday October 13, 2022 @02:00PM (#33194371)
Thanks, fixed
Re:A Database Without Dynamic Memory Allocation by 01100011 on Thursday October 13, 2022 @02:34PM (#33194721)
Nearly everything I learned about embedded systems programming came from either Embedded Systems Magazine articles(https://www.embedded.com/embedded-systems-design-magazine-ar...) or just being a paranoid SoB.

Writing reliable systems means spending a lot of time thinking about what and how failures can occur, minimizing them and dealing with the cases you can't get rid of. Simple is nearly always better.

It also helped to be an EE by education and not a CS major. CS majors can get blinded by their abstractions and forget that they're working on a real piece of HW with complex limitations which require design tradeoffs.

Re:A Database Without Dynamic Memory Allocation by hinkley on Thursday October 13, 2022 @12:49PM (#33193526)
I think it was when I was reading the zlib source code, while dinosaurs roamed outside my college window, where I first saw a variant of this, of a library that statically allocated memory for a graceful shutdown routine before doing anything, so even if memory were exhausted by a bug in other code later on it could still exit cleanly.
Re:A Database Without Dynamic Memory Allocation by colejohnson66 on Thursday October 13, 2022 @01:39PM (#33194167)
IIRC, .NET does something similar for OutOfMemoryExceptions. The exception is thrown before actually running out of memory. That allows just enough space for the OutOfMemoryException object (and associated exception info) to be allocated.
Re:A Database Without Dynamic Memory Allocation by znpy on Thursday October 13, 2022 @03:14PM (#33195230)
Iirc also perl does this. I’m on the phone right now, but iirc $^O was a variable filled with about 20k of worthless data that could be freed in case of an out of memory condition.
Re:A Database Without Dynamic Memory Allocation by gloria_mundi on Friday October 14, 2022 @08:39AM (#33202723)
It's called $^M, and you have to explicitly fill it with garbage.
Re:A Database Without Dynamic Memory Allocation by Merad on Thursday October 13, 2022 @04:14PM (#33195944)
I believe the runtime preallocates an OOME on startup, so if one is encountered it can just fill in the blanks and throw without needing any additional allocations.
Re:A Database Without Dynamic Memory Allocation by melech_ric on Thursday October 13, 2022 @01:15PM (#33193866)
It's still done. I did the code review for the guy who wrote it for our server. If we're crashing due to an OOM situation we still have enough memory to write out a minidump.
Re:A Database Without Dynamic Memory Allocation by touisteur on Thursday October 13, 2022 @01:28PM (#33194027)
The pain here is to test and keep testing these paths. Same as some 'free disk space' monitoring...
Re:A Database Without Dynamic Memory Allocation by melech_ric on Thursday October 13, 2022 @01:58PM (#33194349)
I agree. However, in our case, that pain has proven to be less than not having those minidumps.
Re:A Database Without Dynamic Memory Allocation by touisteur on Thursday October 13, 2022 @05:12PM (#33196552)
Oh for me too, but I wish it was easier, like something supported seriously by my language of choice, my OS of choice or my libc of choice, with the flick of an flag... I don't retest the whole libc and kernel every time (although, err, someone should, right? :-)
Re:A Database Without Dynamic Memory Allocation by jorangreef on Thursday October 13, 2022 @02:01PM (#33194379)
Joran from the TigerBeetle team here!

TigerBeetle uses Deterministic Simulation Testing to test and keep testing these paths. Fuzzing and static allocation are force multipliers when applied together, because you can now flush out leaks and deadlocks in testing, rather than letting these spillover into production.

Without static allocation, it's a little harder to find leaks in testing, because the limits that would define a leak are not explicit.

Re:A Database Without Dynamic Memory Allocation by polskibus on Thursday October 13, 2022 @02:40PM (#33194798)
Can you provide some pointers regarding the simulation testing for more information?
Re:A Database Without Dynamic Memory Allocation by jorangreef on Thursday October 13, 2022 @02:52PM (#33194934)
Sure!

Here's an overview with references to the simulator source, and links to resources from FoundationDB and Dropbox: https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/...

We also have a $20k bounty that you can take part in, where you can run the simulator yourself.

Re:A Database Without Dynamic Memory Allocation by srcreigh on Thursday October 13, 2022 @01:59PM (#33194364)
What language? What does the server do?
Re:A Database Without Dynamic Memory Allocation by melech_ric on Thursday October 13, 2022 @03:14PM (#33195219)
C++ on Windows. It's an old codebase that has been used to run custom test hardware.

We also target Linux with the same codebase, but the environment is a bit different. On Linux we're using a system slice and we're not doing minidumps.

Re:A Database Without Dynamic Memory Allocation by kotlin2 on Thursday October 13, 2022 @02:06PM (#33194426)
What environment is the server running in? Can you prevent the Linux OOM killer from killing your process?
Re:A Database Without Dynamic Memory Allocation by abeyer on Thursday October 13, 2022 @02:34PM (#33194727)
The OOM killer can be disabled completely on a system, or a process can be excluded from it by setting an OOM flag under `/prod/<pid>` It's not generally considered good practice in production, but I think that's largely because most software does not do what is being suggested here... in the scenario where you have, seems like the right thing to do.
Re:A Database Without Dynamic Memory Allocation by throwaway09223 on Thursday October 13, 2022 @03:59PM (#33195792)
It's definitely good practice in production and is often necessary.

The techniques mentioned above will (perhaps surprisingly) not eliminate errors related to OOM, due to the nature of virtual memory. Your program can OOM at runtime even if you malloc all your resources up front, because allocating address space is not the same thing as allocating memory. In fact, memory can be deallocated (swapped out), and then your application may OOM when it tries to access memory it has previously used successfully.

Without looking, I can confidently say that tigerbeetle does in fact dynamically allocate memory -- even if it does not call malloc at runtime.

Re:A Database Without Dynamic Memory Allocation by jorangreef on Thursday October 13, 2022 @04:21PM (#33196020)
We're aware of this, in fact, and do have a plan to address virtual memory. To be fair, it's really the kernel being dynamic here, not TigerBeetle.
Re:A Database Without Dynamic Memory Allocation by throwaway09223 on Thursday October 13, 2022 @06:05PM (#33197056)
Oh for sure. Not casting any shade cast at all on your work - I am really happy to see what you're doing. This kind of thing has a lot of value even in a virtual memory environment.
Re:A Database Without Dynamic Memory Allocation by abeyer on Thursday October 13, 2022 @05:32PM (#33196762)
> It's definitely good practice in production and is often necessary.

I'd be curious if you have any resources/references on what is considered good practice in that now, then.

It's been a long time since I did much ops stuff outside a few personal servers, so it may well be my background is out of date... but I've certainly heard the opposite in the past. The argument tended to run along the lines that most software doesn't even attempt to recover and continue from a failed malloc, may not even be able to shut down cleanly at all if it did anyway, and the kernel may have more insight into how to best recover...so just let it do its thing.

Re:A Database Without Dynamic Memory Allocation by throwaway09223 on Thursday October 13, 2022 @09:47PM (#33198736)
Sure. A lot of systems are single-app systems. They run a single instance of a rdbms, app, etc - or several instances all of which are represent the sole purpose of the system. In these cases it's generally good practice to disable OOM because if the app is killed, nothing else on the system has value anyway.

It would be ideal to not tickle the OOM killer, but it does happen. A great example would be Redis, using bgsave. There's a lot to criticize about redis and bgsave and I don't mean to defend its architecture, this is just an extreme example useful for illustration. Because Redis forks its entire in-memory state and writes itself to disk, it will sometimes appear to the system as if it has doubled in memory use. It's a huge, sudden memory pressure event, exacerbated by any writes forcing CoW allocations while the bgsave runs.

Many other app servers or database systems can have similar sudden memory pressure. You basically don't ever want a primary app on a system to be killed, so it's usually best to just disable OOM killer entirely on these processes.

There's often no failed malloc() in these situations. Often you will see OOM in situations where no malloc() has ever failed, because malloc() is just allocating address space without any attached memory, which is nearly free, and which almost always succeeds. The failure will occur later when the allocated page is first written to -- causing a page fault and an actual allocation to happen. There's no associated function call or system call. Page faults are triggered simply by accessing memory. This is why the OOM killer exists, as there's no function available to return a failure to when memory can't be produced. Such is the idiosyncratic behavior of lazily-allocated memory in modern virtual memory systems.

tldr: Malloc never fails, because malloc allocates address space not memory. Memory writes trigger failures, because writes create page faults and trigger the actual allocations. But the failures often express behavior elsewhere, via the action-at-a-distance magic of the OOM.

Re:A Database Without Dynamic Memory Allocation by blondin on Thursday October 13, 2022 @02:11PM (#33194487)
game development has been doing it this way for a long time too. the domain often dictates how we code.