Hacker News
points by adrian_b 4 days ago | 0 comments
Many allocators must keep 2 sizes for every allocated object: the size of the allocated memory and the size actually occupied by the requested object.

Some allocators keep other extra information for each object. Thus they need all that information and if the "memory free" function gives them only the size of the object, that is not enough.

Since the size is not enough, it is superfluous for the user program to provide it. When given a pointer to an object, the memory allocator will know where to find all the associated data. It might be in the cache line that is immediately before the pointed memory, or it might be in an array, at an index that can be deduced somehow from the value of the pointer, or all the information might be implicit, e.g. when objects are allocated from stacks of objects of the same size, when the size can be deduced from the pointer value, so it is not stored anywhere.

Therefore the correct definition for a memory free function is to have only a pointer as parameter, to give freedom to the allocator to use whichever strategy is deemed better.

[–] OskarS 4 days ago
> Many allocators must keep 2 sizes for every allocated object

And wouldn't it be nice to just make them keep one number! That's a size_t and a couple of cycles saved for every malloc()!

I will say, this is not some far-out niche idea of mine, it's basically universal for all "new" implementations of memory allocators to require exactly this information for deallocation. There's a reason Zig allocators take a slice and not a pointer [0], why std::pmr::polymorphic_allocator::deallocate takes a size [1] and why Rust allocators require it as well (in the layout param) [2]. It's just a better API design.

EDIT: for C++, the better reference is the Allocator named requirement, which the polymorphic allocator is just one example of. But you can see it there as well in the deallocate() method [3]

[0]: https://ziglang.org/documentation/master/std/#std.mem.Alloca...

[1]: https://en.cppreference.com/cpp/memory/polymorphic_allocator...

[2]: https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html#t...

[3]: https://en.cppreference.com/cpp/named_req/Allocator

[–] Sesse__ 4 days ago
> And wouldn't it be nice to just make them keep one number! That's a size_t and a couple of cycles saved for every malloc()!

You can make them keep basically zero numbers per allocation; most modern mallocs use size classes, and can pack objects of the same size class tightly with no metadata inbetween. (You still need some numbers per page/span and such.)

[–] Veserv 4 days ago
No, having the size would be immensely useful. Consider the standard allocator design.

You have a fixed number of small-size block allocators (say 8-256 bytes) and then a sequence of exponentially-sized large block allocators.

For small-size block allocators you want limited allocator overhead, limited over-allocation, high speed, and likely the ability to support certain alignments like 8, 16, and 64 (cache line). Attaching a requested size and actual size (or even just one) to every block would be tremendous overhead.

For large blocks you care less about allocator overhead and over-allocation since it is less overhead as a percentage. So, attaching requested size and actual size to every block is a small cost. However, you likely also want the ability to support certain alignments like 4K, 16K, 2MB, etc. (natural hardware or OS sizes).

So, you generally want two distinct strategys and bookkeeping mechanisms based on the size of the allocation. If deallocation does not take the requested size, then your very first problem is figuring out which mechanism you are using. The problem with this is that you want small-size allocations to have small overhead, so the discrimination mechanism must also be small-overhead.

If you pass in the requested size, then it becomes trivial to distinguish with zero allocator overhead. Is it less than 256 bytes (or whatever small size you choose)? Cool, it is small-size. Round up to the nearest small-size block. Release the memory to the identified small-size block allocator. This has zero per-block overhead, is totally trivial, extremely fast, and preserves alignment.

If the allocated size is in the large-block regime, then you know you can use a per-block overhead scheme. Except, this is still better. If you do not know the requested block size, then you are limited to either a header at negative offset or a auxiliary data structure keyed on the pointer value to determine the actual size. The former messes with your alignments and the latter requires the allocator to be stealing unrelated memory for its own data structures. If deallocation tells you the requested size, then you can easily use a footer scheme instead. Just always over-allocate enough space for a footer and then you can always just jump forward by the requested block size to find the footer and you do not mess up your alignments.

Getting the requested size, which the caller is basically always already remembering via type information, gives you a lot of advantages and optimization levers.

[–] senderista 4 days ago
But sized deallocation gives the allocator implementation more freedom to optimize, not less. More information from clients = more optimization opportunities for the implementation. I don't understand how you can claim the opposite is true.

Source: I have implemented allocators.

[–] mrheosuper 3 days ago
I don't see the point

If sized parameter helps optimization, why allocator just not put it in the header block already ?