I just wonder what is the difference and what is more encourage, the MemoryStack#stackMalloc
and the other static stackXx
methods and the try (var stack = stackPush()) { stack.malloc }
pattern?
I think they do the same. Only that with stackPush
you can have multiple stacks?
The static methods MemoryStack.stackMalloc(int)
, MemoryStack.stackCalloc(int)
(as well as their specializations returning IntBuffer
, LongBuffer
, FloatBuffer
, DoubleBuffer
or LWJGL 3's PointerBuffer
) first call MemoryStack.stackGet()
to retrieve (and potentially initialize) the Thread-Local variable TLS
(in the current implementation) to retrieve a MemoryStack instance for the current thread and then call the instance methods MemoryStack.malloc(int)
, MemoryStack.calloc(int)
or any of the type-specializations on that Thread-Local MemoryStack instance.
Doing this repeatedly will then slowly lead to the "stack" memory in the current thread's MemoryStack instance to deplete with further invocations throwing an OutOfMemoryError
Java exception.
So, if you only ever allocate memory with the static malloc/calloc(int)
methods, and don't do anything else like resetting the stack pointer of the current (and only) stack frame via the instance method MemoryStack.setPointer(int)
to "reclaim" the allocated memory for that MemoryStack, then this MemoryStack instance will run out of memory, soon.
The static method MemoryStack.stackPush()
also does a Thread-Local lookup of the calling thread's MemoryStack instance (with potential initialization if not yet initialized for the current thread) followed by a call to the instance method MemoryStack.push()
to create a new "stack frame".
From the JavaDocs of that MemoryStack.push()
method:
Stores the current stack pointer and pushes a new frame to the stack. This method should be called when entering a method, before doing any stack allocations. When exiting a method, call the pop() method to restore the previous stack frame.
Pairs of push/pop calls may be nested. Care must be taken to:
- match every push with a pop
- not call pop before push has been called at least once
- not nest push calls to more than the maximum supported depth
So, we see that MemoryStack.push()
will create a new "stack frame", which is basically just the current pointer's position. A following MemoryStack.pop()
will then pop the current stack frame off the list/stack of frames and reset the MemoryStack's pointer to the previous position (which it had before push() was called).
Now, the MemoryStack class was designed such that it is usable with the try-with-resources Java statement (which itself has been introduced with Java 7) by implementing the java.lang.AutoCloseable
interface. The implementation of the close()
method for MemoryStack simply does a pop()
on the this
MemoryStack instance in order to pop the current "stack frame" and reset the MemoryStack's pointer to the value of the previous/parent stack frame (which may have been the first/default frame).
So, the different approaches that you wanted to compare really do different things:
MemoryStack.malloc(int)
without a push/pop of the MemoryStack will increment the MemoryStack's pointerMemoryStack.stackPush()
as the resource in a try-with-resources statement will create a new "stack frame" such that the corresponding MemoryStack.pop()
call can "reclaim" the allocated memory (i.e. the difference of the bumped pointer to the previous frame)In essence, you should always push(), allocate memory and then pop(), for a suitable "frame" of allocation in your application. You can do that with the try-with-resources Java statement (or do it manually).
Additionally, I'd like to point you to the LWJGL 3 blog article about "Memory Management in LWJGL" with some more useful information about different allocation strategies and their pros and cons in certain scenarios.
Lastly, the question from your post:
Only that with stackPush you can have multiple stacks?
can be answered by saying that, yes, you can potentially have different MemoryStack instances by either:
MemoryStack.create()
or one of its overloadsMemoryStack.stackGet()
.However, you probably wouldn't really need multiple MemoryStack instances per thread, because by the "nature" of a MemoryStack, it should more or less resemble the call stack of your Java method invocations, which just happen per-thread by definition.