Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

This is a strategy for fighting the GC, but it's also generally a bad strategy.

Allocating a large buffer is literally what an array or vector is. A heap uses a heap structure and hops around in memory for every allocation and free. It gets worse the more allocations there are. The allocations are fragmented and in different parts of memory.

Allocating a large buffer takes care of all this if it is possible to anything else. It doesn't make sense to make lots of heap allocations when what you want is multiple items next to each other in memory and one heap allocation.

That guy I'm talking about did a lot of "performance optimizations" based on gut feelings and not data.

You need to let this go, that guy has nothing to do with what works when optimizing memory usage and allocation.

But I've further observed that when it comes to optimizing for the GC, a large amount of problems don't need such an extreme measure like building your own memory buffer and managing it directly.

Making an array of contiguous items is not an "extreme strategy", it's the most efficient and simplest way for a program to run. Other memory allocations can just be an extension of this.

I agree that many programs with a GC will probably need to change their algorithms to minimize allocations. I disagree that "allocating a large buffer and managing it by hand"

If you need the same amount of memory but need to minimize allocations how do you think that is done? You make larger allocations and split them up. You keep saying "managing it by hand" as if there is something that has to be tricky or difficult. Using indices of an array is not difficult and neither is handing out indices or ranges to in small sections.

 help



> A heap uses a heap structure and hops around in memory for every allocation and free.

Not in the JVM. And maybe this is ultimately what we are butting up against. After all, the JVM isn't all GCed languages, it's just one of many.

In the JVM, heap allocations are done via bump allocation. When a region is filled, the JVM performs a garbage collection which moves objects in the heap (it compacts the memory). It's not an actual heap structure for the JVM.

> It doesn't make sense to make lots of heap allocations when what you want is multiple items next to each other in memory and one heap allocation.

That is (currently) not possible to do in the JVM, barring primitives. When I create a `new Foo[128]` in the JVM, that creates an array big enough to hold 128 references of Foo, not 128 Foo objects. Those have to be allocated onto the heap separately. This is part of the reason why managing such an object pool is pointless in the JVM. You have to make the allocations anyways and you are paying for the management cost of that pool.

The object pool is also particularly bad in the JVM because it stops the JVM from performing optimizations like scalarization. That's where the JVM can completely avoid a heap allocation all together and instead pulls out the internal fields of the allocated object to hand off to a calling function. In order for that optimization to occur, and object can't escape the current scope.

I get why this isn't the same story if you are talking about another language like C# or go. There are still the negative consequences of needing to manage the buffer, especially if the intent is to track allocations of items in the buffer and to reassign them. But there is a gain in the locality that's nice.

> Using indices of an array is not difficult and neither is handing out indices or ranges to in small sections.

Easy to do? Sure. Easy to do fast? Well, no. That's entirely the reason why C++ has multiple allocators. It's the crux of the problem an allocator is trying to solve in the first place "How can I efficiently give a chunk of memory back to the application".

Obviously, it'll matter what your usage pattern is, but if it's at all complex, you'll run into the same problems that the general allocator hits.


In the JVM, heap allocations are done via bump allocation.

If that were true then they wouldn't be heap allocations.

https://www.digitalocean.com/community/tutorials/java-jvm-me...

https://docs.oracle.com/en/java/javase/21/core/heap-and-heap...

not possible to do in the JVM, barring primitives

Then you make data structures out of arrays of primitives.

Easy to do? Sure. Easy to do fast? Well, no. That's entirely the reason why C++ has multiple allocators.

I don't know what this means. Vectors are trivial and if you hand out ranges of memory in an arena allocator you allocate it once and free it once which solves the heavy allocation problem. The allocator parameter in templates don't factor in to this.


> If that were true then they wouldn't be heap allocations.

"Heap" is a misnomer. It's not called that due to the classic CS "heap" datastructure. It's called that for the same reason it's called a heap allocation in C++. Modern C++ allocators don't use a heap structure either.

How the JVM does allocations for all it's collectors is in fact a bump allocator in the heap space. There are some weedsy details (for example, threads in the JVM have their own heap space for doing allocation to avoid contention in allocation) but suffice it to say it ultimately translates into a region check then pointer bump. This is why the JVM is so fast at allocation, much faster than C++ can be. [1] [2]

> I don't know what this means.

JVM allocations are typically pointer bumps, adding a number to a register. There's really nothing faster than it. If you are implementing an arena then you've already lost in terms of performance.

[1] https://www.datadoghq.com/blog/understanding-java-gc/#memory...

[2] https://inside.java/2020/06/25/compact-forwarding/


Modern C++ allocators don't use a heap structure either.

"Yes, malloc uses a heap data structure to allocate memory dynamically for programs. The heap allows for persistent memory allocation that can be managed manually by the programmer."

"How Malloc Works with the Heap

    Heap Data Structure: Malloc uses a heap data structure to manage memory. The heap is a region of a process's memory that is used for dynamic memory allocation.

    Memory Management: When you call malloc, it searches the heap for a suitable block of memory that can accommodate the requested size. If found, it allocates that memory and returns a pointer to it."
How the JVM does allocations for all it's collectors is in fact a bump allocator in the heap space.

This doesn't make sense. It's one or the other. A heap isn't about getting more memory or mapping it into a process space, it is about managing the memory already in the process space and being able to free memory in a different order than you allocated it, then give that memory back out without system calls.

https://www.geeksforgeeks.org/c/dynamic-memory-allocation-in...

https://en.wikipedia.org/wiki/C_dynamic_memory_allocation

JVM allocations are typically pointer bumps, adding a number to a register.

I think you are mixing up mapping memory into a process (which is a system call not a register addition) and managing the memory once it is in process space.

The allocator frees memory and reuses it within a process. If freeing it was as simple as subtracting from a register then there would be no difference in speed between the stack and the heap and there would be no GC pauses and no GC complexity. None of these things are true obviously since java has been dealing with these problems for 30 years.

This is why the JVM is so fast at allocation, much faster than C++ can be

Java is slower than C++ and less predictable because you can't avoid the GC which is the whole point here.

The original point was that you have to either avoid the GC or fight the GC and a lot of what you have talked about is either not true or explains why someone has to avoid and fight the GC in the first place.


You're wrong for like 6 different reasons.

Java does do bump pointer allocation. The key is that when GC runs, surviving objects get moved. The slow part of GC isn't the allocation (GCs generally have much faster allocators than malloc). The slow part is the barriers that the GC requires and the pauses.


You're wrong for like 6 different reasons.

If that were true you could have listed one the made sense in context. This person was saying that allocation was as fast as a incrementing a register while continually ignoring the fact that deallocation needs to happen along with any organization of allocated memory.

Then they were ignoring that large allocations have big speed benefits for a reason.

Conflating java moving a pointer, mapping memory into a process, sbrk, and arena allocation is going in circles, but the fundamentals that people need to fight the GC or work around it remains.

Allocations have a price and the first step to optimizing any program is avoiding that, but in GC languages you get pauses on top of your slow downs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: