Live data from Hacker News

All About Libpas, Phil's Super Fast Malloc

github.com

51–60 of 87 posts

Re: All About Libpas, Phil's Super Fast Malloc

#51

Are there any projects that surgically augment the memory APIs to be more cooperative? Sure `malloc` implementations will make various tradeoffs, but what if the malloc/free APIs were expanded to expose more programmer-intent or cooperative defrag, etc? - Perhaps `malloc` could ask for an intended lifecycle (think GC generation) that could swap arenas or other algo internals. This opens us up to meta-allocators. - Pe…

I've thought about this a lot. I think that overall, malloc/free/new/delete are already so hard to use that if you added more stuff, it would create too much cognitive load for the programmer. The result would be that the hints the programmer gave you would be more wrong than the malloc's best guess.

Let me go through these point by point and offer some thoughts.

- Perhaps `malloc` could ask for an intended lifecycle (think GC generation) that could swap arenas or other algo internals. This opens us up to meta-allocators.

It could, and if that information was accurate, then it would be profitable. But if that information was inaccurate, then you'd get worse perf and worse memory usage than if you let malloc just do whatever it wants. Also, anything like this creates more fragmentation since it creates a new opportunity for the programmer to force the allocator to pick something other than the first fit.

- Perhaps program lifecycle hooks that give allocators more context into how already-allocated memory intends to be accessed. ("Done bootstrapping the server, so now new memory will be accessed by single threads and only live for one request", etc)

If you convey this information after you've already allocated the object then there isn't much the malloc could do. If the malloc uses this information to decide where the next malloc call puts memory then you've created a fragmentation risk.

- Perhaps the programmer could periodically call `remalloc` to allow objects to be moved or promoted between arenas/generations.

Yes, they could, and this is a great idea. I believe that calling realloc (or just mallocing a new object, memcpying or copy-constructing, and freeing the old one) relieves fragmentation because it gives the malloc an opportunity to put the object in a more profitable location.

- Perhaps mallocs/frees could be more intelligently batched (where explicit structs/arrays don't normally make sense) for surprise-free defrag or OS allocation.

libpas batches allocation and deallocation. These strategies help performance. They have only a small effect on memory usage (and that effect is that you use a bit more memory by batching).

- Intelligent pairing of malloc/free with explicit notions of intended re-use; some indication of "I will free this and ask for the same memory again in the same call" or auto-struct-ing mallocs which know that we tend stack malloc(foo);malloc(bar);free(bar);free(foo). Avoid any actual OS/memory involvement whatsoever.

Allocators like libpas (and mimalloc and many others) have very cheap malloc/free calls that perform great in these situations. They don't do any locking or call into the OS in the common case when you do this.

- Perhaps `identmalloc` could handle common flyweight patterns of instantiate-unless-exists; e.g. intelligently turn shared state into offsets into a contiguous arenas. This may be more useful in C++ with existing conventions around copy-by-value constructs.

I think that most fast mallocs like libpas (and others) make this situation cheap enough that you don't need anything else.

- Some of these are indeed components of existing mallocs/allocators (e.g. libpas is type-aware), but my point is that allocators have to jump through hoops to derive heuristics, and these heuristics often amount to "the programmer often intends to use memory like X." Performance engineers then pattern-match for X rather than cooperating with the compiler to expose X intrinsically from the beginning. The above ideas impose slightly different programming paradigms, so there's still the white wale of a perfect drop-in heuristic, but it seems less subject to temporary or local maxima.

If the program is simple enough that you understand the object lifetimes completely, or you have enough resources to throw at the problem that you can completely understand lifetimes even for a complex program, then the best path forward is to just not call malloc. Lay out your memory a priori or write a highly customized "allocator" (that may not even have an API that looks anything like malloc).

If the program is too complex for such reasoning, then I believe there's no way for the programmer to tell the malloc anything it can't deduce with heuristics. The programmer can say some stuff, but when you have a large sophisticated heap, then the programmer will not be able to predict what it is about the heap that creates pain for the malloc. In most experiments where I've tried to do this kind of tuning, I end up with something that runs slower or uses more memory, and the investigation usually leads me to learn that my hypothesis about the malloc's pain point was totally wrong.

- So: Are there compelling (albeit slightly less general-purpose) alternatives where the allocator and programmer communicate additional hints or other API contracts that allow the allocator to move away from heuristics and into proven patterns? Or is this basically akin to "use c++/smart pointers or just get a gc"?

Maybe! I'm not proving a negative here. But everything I've tried in this area has been a negative result for me.

Re: All About Libpas, Phil's Super Fast Malloc

#52

This looks awesome and I really like the tone of the documentation. Great work! At the same time I'm sort of worried, that such a deep change in the memory management of my day-to-day browser opens up a significant security risk for a considerable time, until new bugs are found and closed. I'm sure this got more than one pair of eyes at apple, but I'd really welcome a specific bug bounty on such a change...

That's a fair point. Code churn creates bugs. That's a good argument for slowing down the pace of browser development overall, except maybe the kind of development that focuses on security hardening.

Libpas has some security features that bmalloc didn't have. It goes further than bmalloc in avoiding inline metadata (bmalloc had an IsoHeap thing that used scrambled inline freelists). Also, libpas opens the door to isoheaping all allocations in WebKit (see https://bugs.webkit.org/show_bug.cgi?id=231938), which would be a big security win.

Also, libpas has been fuzzed to death. In test_pas, there's a bunch of fuzz tests I call "chaos" and I've run those for extended periods of time (days, weeks) after major changes. Of course it still has bugs, but considering how insane the heap organization is, libpas also makes attackers' lives much harder in the short term.

The combination of libpas's enhanced security features and the fact that it substantially changes heap layout might be enough to cancel out the fact that it has increased bug tail. And... if you're worried about bug tail from major changes introducing security vulns then there are probably much worse major changes that someone could do.

Re: All About Libpas, Phil's Super Fast Malloc

#53

General purpose memory-allocation is a lie. Everything has use cases where they're faster than other libraries. I think that's why we keep seeing newer malloc schemes pop up, because the performance of the heap 100% depends on the use-case, and different people have different use cases. Still, studying everyone else's heaps (and garbage collectors, a closely related discussion) is probably good for high-performance p…

I've seen a few opensource projects archive their custom allocators because they were not beating the system's one or jemalloc. So if you have the skill and time then yeah go for it, else stick with general purpose ones.

There are a couple of approaches that might legitimately be simpler than using a general-purpose allocator: in a single-pass batch process, just throw things on the floor and let the OS sort it out on exit[1] (I think the D compiler does this); in a multiple-pass batch process, make each pass litter its own room (arena, obstack, etc.) then demolish said room when done (GCC does this or at least did in the past).

On the other hand, these may require rearranging the logic somewhat to fit them, so the question of how much rearrangement to tolerate still remains. And, well[4],

  /*
   * We divy out chunks of memory rather than call malloc each time so
   * we don't have to worry about leaking memory.  It's probably
   * not a big deal if all this memory was wasted but if this ever
   * goes into a library that would probably not be a good idea.
   *
   * XXX - this *is* in a library....
   */
[1] I am rather dismayed by how Raymond Chen advocates for this for memory[2] but insists it could not possibly be a good idea for Windows GDI handles, no, you stupid sloppy programmer[3]. Maybe because he was involved in GDI from the other side? (Of course, for file descriptors on Unix it’s still the standard practice.)

[2] http://bytepointer.com/resources/old_new_thing/20120105_006_...

[3] http://bytepointer.com/resources/old_new_thing/20051014_305_...

[4] https://github.com/the-tcpdump-group/libpcap/blob/4c1e516dd2...

Re: All About Libpas, Phil's Super Fast Malloc

#54

I admit I'm a sucker for metacircularity, but have to ask: > The bootstrap heap has hacks to allow itself to allocate that array out of itself. Not quite following that, can someone elaborate?

The bootstrap heap has a freelist that is just an array of entries that tell you where the free memory is.

That array has to be allocated somewhere.

All pas_simple_large_free_heaps allocate that array from the bootstrap heap. Including the bootstrap heap. So, the pas_simple_large_free_heap has hacks that go something like: "if I'm allocating or freeing something and I need to allocate or free the free list, then very carefully also perform that allocation/deallocation in tandem with the one I'm doing". The main hack there is just that there's some statically allocated "slop" for the boostrap free list, so if you want to add to it in the middle of an allocation, you add it to the slop array, and then after you're doing you reallocate the free list and copy stuff from slop into it.

Re: All About Libpas, Phil's Super Fast Malloc

#56
post #21

Earlier quoted context omitted.

Quoted post unavailable.

Sometimes things are named after the maker ex post facto because they need a way to distinguish it. The Bourne shell, mentioned here, is likely called that for the same reason the very first shell was called the Thompson shell, in that they were written by a single person originally and not really given a name (the original being from Ken Thompson), since they were just different (or the original) implementations of…

"Linux" was forced on him.

Re: All About Libpas, Phil's Super Fast Malloc

#57
post #50

Earlier quoted context omitted.

Oh for sure. Premature optimizing is usually bad form. What is tricky about performance issues in allocation is that they can be hard to profile. There are tools for analyzing GC performance in GC'd languages, but sometimes malloc/free can just be a big black box.

Premature optimization is not bad form when it's re-framed as good architecture. So it's not 'usually bad form' to architect something from the outset, using your experience, and that's something everyone understands. This pervasive disdain for premature optimization leads to bad architecture that often leads to expensive rewrites. So because the word optimization is so overloaded and treated with disdain it feels li…

That's one reason I point out a few comments above that certain niches will need to take it into account ahead of time. Eg. An a/v application will typically allocate all buffers up front and re-use them frequently rather than return them to the allocator. A lot of server applications will want to keep per-client memory usage low. For general purposes, there's the general purpose allocator.

Re: All About Libpas, Phil's Super Fast Malloc

#59

Earlier quoted context omitted.

For other people who have never heard of bmalloc - it's a custom allocator used only by WebKit. I guess it's not surprising they added one since the Mac system allocator is extremely slow. A custom allocator is pretty much a free 20% speed up on Mac (depending on your workload) but I found they made no difference on Linux. Haven't tried on Windows.

Just some credit where credit is due. I measured the system malloc crushing all other mallocs on some workloads and they were the kind of workloads that some folks run every day. I measured the system malloc crushing most other mallocs on memory efficiency on most workloads. System malloc is really good at reusing memory and has very mature decommit policies that easily rival what I came up with. So there’s that.

What sort of workloads are those? I have yet to see a workload where macOS's system allocator is not substantially slower than alternatives like jemalloc.

Re: All About Libpas, Phil's Super Fast Malloc

#60
post #58

Will this have support for macOS by inserting itself, as jemalloc does, as the default malloc zone? If so, does that mean it will be very fast at determining if a given pointer was allocated by libpas?

libpas can do that, though some of the code to do it is not in the WebKit repo (but the enumerator support - a complex component that is necessary to fully replace system malloc - is in the repo).

libpas can already determine if it owns a pointer in O(1) time and no locking. It uses megapages for this purpose. Though, right now, it's not configured to fully leverage this (if you pass an unknown ptr to free then as a last step it'll lock the heap_lock even though it would be a small change to just use megapages there).

Post reply on HN