Using custom allocator in C

603 views
Skip to first unread message

Vlad Saenko

unread,
Sep 17, 2020, 5:26:43 AM9/17/20
to FlatBuffers
Hey guys,
In our project (in C99) we have our own custom way of memory management

Regarding to https://github.com/dvidelabs/flatcc#custom-allocation we have two mechanisms of providing custom allocation for flatcc-builder in C:
  • redefine malloc/free macros
  • provide special flatcc_builder_alloc_fun to flatcc_builder_custom_init.
----------------------------------------------------------------------------------------------------------------------

So my questions are:
1) what is the correct way to redefine these macros (FLATCC_ALLOC/FLATCC_FREE etc.) that compiler gets my own functions instead of those provided by flatcc
E.g., I've created some "fb_common.h" where I'm using smth like this:

```
#pragma once
#ifdef FLATCC_ALLOC
#undef FLATCC_ALLOC
#endif
...
#include "some_schema_builder.h"

void* custom_alloc(size_t size);

#define FLATCC_ALLOC custom_alloc
// or #define FLATCC_ALLOC(size) custom_alloc(size)
...
```

custom_alloc is implemented in some source file (like "fb_common.c")

I include this header the first in all files of project and everything compiles successfully. 
But in debug I see the next:
  • `flatcc_builder_finalize_aligned_buffer` which I use for final allocation calls inside FLATCC_BUILDER_ALIGNED_ALLOC 
  • this macro corresponds to __portable_aligned_alloc but not my custom function.
What I'm doing wrong here?

----------------------------------------------------------------------------------------------------------------------  

2) Regarding to the second the more "cool" way with flatcc_builder_custom_init - is there any example? I saw flatcc_builder_default_alloc but does it really useful if I anyway call flatcc_builder_finalize_aligned_buffer finally? (which invokes FLATCC_BUILDER_ALIGNED_ALLOC inside). It's a bit confusing 

Wouter van Oortmerssen

unread,
Sep 17, 2020, 12:31:09 PM9/17/20
to Vlad Saenko, FlatBuffers, mikkelfj
+Mikkel

--
You received this message because you are subscribed to the Google Groups "FlatBuffers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flatbuffers...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/flatbuffers/58c1e65c-aa0d-4e39-a9f8-1fa1b05b01bcn%40googlegroups.com.

mikkelfj

unread,
Sep 17, 2020, 3:13:25 PM9/17/20
to FlatBuffers
Hi Pluto,

The short answer is that if you have no memory constraints, but just need to use something that is not malloc, then use the macros. If you need detailed control over either very small amounts of memory allocations, or if you need detailed control over how a buffer fails when out of memory, then use a custom allocator. And you may also want to consider a custom emitter. The custom allocator deals with tempory stacks. The emitter is deals with collecting fragments of completed buffer bytes and place the in contiguous memory one way or the other. The default emitter stores data in pages, and finalizes by copying pages to a fresh memory block, then recycles the  pages for the next buffer.

As to your second question on aligned allocation, I'll have to dig deeper - I'd be happy to help once I get a better understanding and you decide what you need. But I can say this: buffer finalization has _nothing_ to do with the custom allocator because the finalization step deals with the emitter objects buffer allocation, and the custom allocator deals with temporary stacks before data is emitted from the builder to the emitter. If you provide a custom emitter that just writes to a fixed preallocated memory block that is adequately aligned, you can get away with a memmove to align the buffer (as an example), but you'd have to make sure aligned_free calls in the builder understands this concept - which you should be able to do by overriding the ALIGNED macros unless something is missing in the API.

There are different kinds of ALLOC/FREE macros but they all fall back to one central set of macros by default that makes it easy to replace malloc, realloc, free, aligned_alloc and aligned_free. See the include directory for those default alloc macros.

Longer story:

Regarding the differences there are some historical, and some real reasons for the differences.
Originally the custom allocator was provided to accommodate really constrained environments where you might want to use fixed block allocations that cannot grow, or where you might want to raise some controlled error in case you cannot allocate memory without having to check for return values on all builder calls.
There is only one example of how to use it, and that is the default allocator implemented in the builder code.
The custom allocator takes a memory type as argument because flatcc uses around 7 or so different stack types, mean of which are not used, or will only use a small number of bytes. You can profile you code to see how much you need and than do a fixed preallocation so the stacks never have to grow, and fail hard if the limit is exceeded, just as an example. The default implemention uses malloc to provide memory, and more recently FLATCC_BUILDER macros to abstract away the allocation.

The emitter was, and is, designed to handle high performance buffer transmission where you can ship data before the buffer is complete, but it doesn't work too well with standard flatbuffers. A hypothetical modified flatbuffer version known as StreamBuffers would write buffer data front to back instead of back to front, which makes streaming much simpler. The emitter can also be used to store data on disk direktly, but again, without StreamBuffers there is a final step collect fragments due to the nature of how FlatBuffers are constructed.

At some point in time, someone figured out that the malloc did not exist on FreeRTOS and needed an alternative. In this case, the problem was not that there needed to be more granular allocation control. The default allocator which was fine, but it should call something different from malloc to provide OS memory. By adding ALLOC macros, the system allocator could be renamed and the code would compile as desired.

Later, someone figured out the hard way, the if you can override allocation, but rely on free to free memory from the finalize function, then you might mix up different allocators during linking. Therefore the builder also provides flatcc_builder_free calls.

It is also worth noting that the emitter is pluggable similar to the custom allocator, but it is not the same thing even if it also allocates memory during build.

The aligned allocation has the extra complication that C11 defines aligned_alloc but says that memory should be deallocated with free. It is impossible to create and efficient backport to older systems without a custom aligned_free function on Windows and some other systems. Therefore the non-standard aligned_free which defaults to free on POSIX. And then macros build on top of that to abstract further.

mikkelfj

unread,
Sep 17, 2020, 3:20:54 PM9/17/20
to FlatBuffers
So, after this long explanation:

The correct way to override the macros is to either replace the include file that defines the low level alloc macros, or to define them in the build system, or to add them in a config.h file. All alloc macros will test if their name has already been defined and stand down if that is the case.

For example you can edit this file and include a custom include file

https://github.com/dvidelabs/flatcc/blob/master/config/config.h
if you want to override the flatcc compiler allocator (not runtime)

or
if you want to override the builder and emitter allocators.

or you can replace this file:
https://github.com/dvidelabs/flatcc/blob/master/include/flatcc/flatcc_alloc.h
or you can use -DFLATCC_...=my_function in the build system.

mikkelfj

unread,
Sep 17, 2020, 3:25:19 PM9/17/20
to FlatBuffers
  • `flatcc_builder_finalize_aligned_buffer` which I use for final allocation calls inside FLATCC_BUILDER_ALIGNED_ALLOC 
  • this macro corresponds to __portable_aligned_alloc but not my custom function.
You are probably overriding the macro at the wrong level. Each subsystem can have it its own allocator but they all default to the flatcc_alloc.h defined macros. If redefining in the file does not work, flatcc probably needs to fix something.
See also emitter.c this is the logic that creates the final buffer as I recall.

Reply all
Reply to author
Forward
0 new messages