95 points g0xA52A2A 2 days ago 36 comments
tyushk 2 days ago | parent
Is the final pattern described actually safe for multi-threaded use? Something like the stack has exactly 1 byte left, T1 allocates the dynamic end with 1 byte (dynamic object just happened to be 1 byte, say) and T2 allocates the fixed size end with 1 byte. Wouldn't they end up overlapping in that case?
saagarjha 2 days ago | parent
hnav 2 days ago | parent
singron 2 days ago | parent
It's a little tricky with memory orders since you are synchronizing on two different locations. The simplest thing is to make all atomic accesses seqcst, but I think a single fence(seqcst) before checking overlap is sufficient in this case and will use fewer synchronizing instructions.
The overflow checks are also tricky. We want to make sure that if an allocation fails, subsequent allocations also fail (even on other threads), and overflow might allow them to think they succeeded by incrementing again back into a valid range. This means you generally can't use fetch_add in a thread-safe bump allocator and instead need to use a CAS loop where you detect overflow before writing.
Since you are doing a CAS loop anyway and always accessing both variables, it might actually be faster to combine both variables, especially if you typically allocate from both at the same time. E.g. if you pack two offsets into a 64-bit CAS word, you can still address 4GB, and you can get even more by aligning allocations. You also don't have to worry about seqcst.
moring 2 days ago | parent
> Just like the other stack allocators we talked about, this one can also work in a multi threaded environment. You might be tempted to make the two updates transactional – like with a compare and swap loop – but it isn’t needed.
Unfortunately they don't explain HOW this is going to work...
saagarjha 2 days ago | parent
pixelpoet 2 days ago | parent
monocasa 2 days ago | parent
HexDecOctBin 2 days ago | parent
This doesn't make a lot of sense. Solving `x <= X, y <= Y` is much easier than solving `x + y <= Z`. If you put a limit on both the size of in-core level data (perhaps using a Cell based system) and on character data in the asset pipeline itself, that is much better than to ensure that all the various combinations of level sizes and character sizes are compatible and won't end up trampling over each other.
saagarjha 2 days ago | parent
HexDecOctBin 2 days ago | parent
saagarjha 2 days ago | parent
Woodi 2 days ago | parent
Also worth to mention that in stack fast "deallocation" memory (usually) is not zeroed or cleared so it is very internal architecture...
Panzerschrek 2 days ago | parent
How? As I understand, for such double-headed allocator to be multithreaded one needs to update two counters atomically, which is tricky.
Panzerschrek 2 days ago | parent
In such case stack-like allocator isn't the best strategy. One can use some sort of cyclic allocator instead. As I know Quake did this for surfaces cache.
hgs3 1 day ago | parent
[1] https://github.com/id-Software/Quake-III-Arena/blob/master/c...
bob1029 2 days ago | parent
There are a lot of other problems to worry about in game dev. Manual memory management is a luxury mostly exclusive to larger studios. Solo developers won't make to the actual runway if they try to fight these kinds of technical battles on principled terms. You often need a lot of samples (paying customers / large QA team) to prove these custom schemes are viable. For better or worse, the cranky old GC might be the lowest risk option. A few late frames is a hell of a lot less disruptive to the gamer than a crash.
rowanG077 2 days ago | parent
kg 2 days ago | parent
I think the average player would not appreciate hourly crashes, especially in games without aggressive autosave - that potentially means having to replay 10+ minutes worth of gameplay on a regular basis because your timing was bad, when you consider how a typical game is built. You could compensate for this in some game types I guess, like in a competitive FPS you'd restart the client software between matches and hope a match never lasts long enough to crash. But that's no substitute for real robustness, IMO.
rcxdude 2 days ago | parent
rowanG077 1 day ago | parent
momocowcow 2 days ago | parent
The real drawback to GC is the inability to estimate your peak memory consumption
saagarjha 2 days ago | parent
stevekemp 1 day ago | parent
That said if you look at a lot of the basic GC approaches you end up either moving all the allocated and live objects, or running through a mark & sweep dance which gives you holes in your allocation region - and causes problems with fragmentation.
Choosing the right kind of GC, knowing your constraints, could probably sidestep these problems, but there seems to be nothing obvious with the simple two-headed bump-allocator decribed in the post, so long as the lifecycles don't change over time - ruining the simplicity of the arena-based approach.
hn_submit 2 days ago | parent
akoboldfrying 2 days ago | parent
For this 2-category case, the double-headed stack is strictly better than maintaining 2 separate half-size stacks, since it permits every sequence of allocations and deallocations that the latter permits, while there are sequences that it permits that the latter does not permit (namely, any sequence in which more than half the memory is temporarily needed by one of the categories, and the other category is temporarily not using it).
windenntw 2 days ago | parent
Sometime in 95 or 96 recall making a very very simple OS patch that would force all allocations > X size ( 1KB or something like that ) to use MEMF_REVERSE and that made wonders to reduce memory fragmentation :)
kevin_thibedeau 2 days ago | parent
tom_ 2 days ago | parent