• Advertisement
Sign in to follow this  

The Best Damn Memory Management Tools, Ever.

This topic is 424 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been working on updating my Epoch programming language to a 64-bit native implementation.

As a few of you old-timers may remember, Epoch began right here on the GDNet forums as a counter to C++ and a pragmatic language for making demanding software like games.

My journey has led to revisiting the 32-bit garbage-collected model that Epoch currently uses. You can find my notes on GitHub.


What I'd like to discuss is whether or not the approach I'm taking to memory management is sane.

- Am I taking on too many conflicting features?
- Is there something I'm not taking into account?
- Would you want to write code in this language, assuming the features are attained as set forth in the notes?

Share this post


Link to post
Share on other sites
Advertisement
I'd look into the new C++ core guidelines / C++ GSL projects to see how they're trying to improve the ease of writing correct memory management. A bunch of it basically involves tagging pointers with promises from the programmer. E.g. This raw pointer has ownership and will be used to manually delete the allocation.

They recommend using raw pointers only for non-owning situations where the lifetime of the pointer is known to be less than the lifetime of the allocation.
One thing you could do in your language, is actually keep track of these promises via an opt-in feature. If someome's "shorter lifetime user" type pointer still exists when the allocation is deleted, a "dangling pointer" assertion should fail.

Another scheme to look at is DICE's scope stacks. Scopes are basically tied to a portion of a stack allocator (aka a mark in mark and release), and act like a collection of unique pointers. i.e. the scope has unique ownership of every object created in it, and those objects are deleted when the scope is deleted.
I embraced these in my engine, and use them for almost everything! It forces you to declare object lifetimes up-front, and then treat all pointers as "shorter lifetime users".
Even in Lua I use scope allocations most of the time -- native objects can be created with GcNew.MyClass() for Lua GC management, or myScope:MyClass() for scope-stack management.

Share this post


Link to post
Share on other sites

It's unclear to me what the goal of Epoch is, how it gets there and at what cost. A lot of things are implied, but it's not always clear what the boundaries are, e.g. "Leave behind primitive build models - and gain a linker-free, single-pass compilation model with full program optimization by default". So I guess the big thing here is no more header files, but does that also mean faster compilations? Is compilation speed seen as something that should be heavily improved?

 

There might be things you think are obvious or are already part of the language, but seeing as you're comparing this to C++ I think they deserve a mention in the readme. Like for example built-in support for arrays? Can I get a hell ye-- I mean length please.

 

Personally I don't see much on the list that would make me want to switch to the language. Things I'd find interesting off the top of my head: no more header files, fast compilations, introspection, built-in support for arrays and bounds checking.

 

Memory management is not on that list, because I haven't really formed an opinion on that. A different approach to memory management problems that I saw in some presentation by Stroustrup, which I found interesting, is not so much trying to prevent every bad thing from happening, but detecting them in as many cases as you can. That might be of interest to you. In cases where I don't want to think about memory at all, I just reserve a large enough static chunk and use that, memory is cheap on the PC platform nowadays :).

 

Hope that's helpful in some way.

Share this post


Link to post
Share on other sites

A bunch of it basically involves tagging pointers with promises from the programmer. E.g. This raw pointer has ownership and will be used to manually delete the allocation.

That functionality of C++/GSL is largely taking the successes of Rust's borrowed pointers and migrating (a by-convention version of) them into std C++. Once we have a decent ownership-checker in the C++ compiler toolchain...

 

It's worth pointing out that Rust had an optional garbage collector up until quite late in the development cycle, but they decided the borrow checker had enough benefits to scrap garbage collection entirely.

 

You aren't necessarily targeting such a constrained problem space as Rust, but I think you'd benefit from taking Rust and its history into account as you design Epoch. My feeling here is that C++-like RAII is rapidly becoming a minimum bar for a language which wants to play in the systems programming space.

Share this post


Link to post
Share on other sites

I'd look into the new C++ core guidelines / C++ GSL projects to see how they're trying to improve the ease of writing correct memory management. A bunch of it basically involves tagging pointers with promises from the programmer. E.g. This raw pointer has ownership and will be used to manually delete the allocation.

They recommend using raw pointers only for non-owning situations where the lifetime of the pointer is known to be less than the lifetime of the allocation.


I've studied the C++ methodologies fairly closely for years. I don't really see C++ doing anything interesting here, mostly just limping along trying to keep up with the rest of the programming language world.

In fairness, there's way more inertia in C++; but on the flip side of that coin, that inertia is why I'm developing a language instead of trying to write "good C++".


I guess the succinct and honest version is I hate C++ a lot.




One thing you could do in your language, is actually keep track of these promises via an opt-in feature. If someome's "shorter lifetime user" type pointer still exists when the allocation is deleted, a "dangling pointer" assertion should fail.


This is a strong candidate for things I'll explore, but I don't relish polluting the type system with lifetime data the way that Rust has done. I'd rather it be more orthogonal.

Granted, there's probably a reason Rust did what it did, but I'm still going to at least try :-)


Another scheme to look at is DICE's scope stacks. Scopes are basically tied to a portion of a stack allocator (aka a mark in mark and release), and act like a collection of unique pointers. i.e. the scope has unique ownership of every object created in it, and those objects are deleted when the scope is deleted.
I embraced these in my engine, and use them for almost everything! It forces you to declare object lifetimes up-front, and then treat all pointers as "shorter lifetime users".
Even in Lua I use scope allocations most of the time -- native objects can be created with GcNew.MyClass() for Lua GC management, or myScope:MyClass() for scope-stack management.


I've used similar schemes in the past as well, with usually good results. I guess I mentally lumped them in with the other strategies but there's no reason not to explicitly call them out I guess.

 
 

It's unclear to me what the goal of Epoch is, how it gets there and at what cost.


Epoch is designed to be a language I don't hate. That's all there is to it.

In practice, this has a lot of implications, most of which are undocumented and almost all of which are fuzzy subjective properties.


A lot of things are implied, but it's not always clear what the boundaries are, e.g. "Leave behind primitive build models - and gain a linker-free, single-pass compilation model with full program optimization by default". So I guess the big thing here is no more header files, but does that also mean faster compilations? Is compilation speed seen as something that should be heavily improved?


The short answer is yes, compilation speed matters a lot to me. I've spent a lot of time getting the compiler faster, and I plan on investing even more time on the 64-bit implementation.


There might be things you think are obvious or are already part of the language, but seeing as you're comparing this to C++ I think they deserve a mention in the readme. Like for example built-in support for arrays? Can I get a hell ye-- I mean length please.


I'm not interested in improving the README right now. I'm interested in the memory management side. There's always going to be things I don't put in the README that someone thinks should be there.

Not to be overly antagonistic or single you out or anything; I just want to stay on topic.

 

Personally I don't see much on the list that would make me want to switch to the language.


I don't care?

This isn't for you. It's for me. And if someone else likes it enough to program in it, cool.

But again, that's not the subject of the thread.


Things I'd find interesting off the top of my head: no more header files, fast compilations, introspection, built-in support for arrays and bounds checking.


All of which are either in the trunk 64-bit implementation or on the way.

 

Memory management is not on that list, because I haven't really formed an opinion on that. A different approach to memory management problems that I saw in some presentation by Stroustrup, which I found interesting, is not so much trying to prevent every bad thing from happening, but detecting them in as many cases as you can. That might be of interest to you. In cases where I don't want to think about memory at all, I just reserve a large enough static chunk and use that, memory is cheap on the PC platform nowadays :).
 
Hope that's helpful in some way.


Considering what I wanted to talk about was memory management, no, that really wasn't terribly constructive, sorry :-/

 
 

That functionality of C++/GSL is largely taking the successes of Rust's borrowed pointers and migrating (a by-convention version of) them into std C++. Once we have a decent ownership-checker in the C++ compiler toolchain...
 
It's worth pointing out that Rust had an optional garbage collector up until quite late in the development cycle, but they decided the borrow checker had enough benefits to scrap garbage collection entirely.
 
You aren't necessarily targeting such a constrained problem space as Rust, but I think you'd benefit from taking Rust and its history into account as you design Epoch. My feeling here is that C++-like RAII is rapidly becoming a minimum bar for a language which wants to play in the systems programming space.


And Rust is one language I've followed closely since (IIRC) shortly after it was publicly unveiled; I continue with Epoch's development largely because I don't really care for Rust's semantics per se.

RAII (or a suitable imitation) is also called out in the linked article as a feature I specifically want to emulate.

Share this post


Link to post
Share on other sites

I don't really care for Rust's semantics per se

Semantics or syntax?

 

The latter I find woefully lacking. The proliferation of unnecessary type annotations is a very high barrier to entry. Coupled to the lack of a decent IDE (a common problem for young languages), and you spend a lot of time trying to find the right incantation to satisfy the type checker...

Share this post


Link to post
Share on other sites

I misinterpreted your last question as a general interest gauge. Just to clarify what I meant with detection vs prevention, it's the C++ core guidelines Hodgman mentioned.

Share this post


Link to post
Share on other sites

I don't really care for Rust's semantics per se

Semantics or syntax?
 
The latter I find woefully lacking. The proliferation of unnecessary type annotations is a very high barrier to entry. Coupled to the lack of a decent IDE (a common problem for young languages), and you spend a lot of time trying to find the right incantation to satisfy the type checker...


Both, to be honest. I think the syntactic burden arises from the semantic design of the language. If lifetime semantics were less coupled to the type system, the syntax would be cleaner and the overall results would (IMO) be much easier to work with.

Share this post


Link to post
Share on other sites
To maybe frame this in a bit more concrete terms, here's what I'm thinking as of now...


- Stack allocated, deterministically-destroyed objects are already a part of Epoch. What is missing is the notion of destructors, i.e. callbacks on the destruction event of a singular object.
- Adding destructors on either a per-type or per-variable level would give Epoch essentially the equivalent of RAII semantics.
- That leaves dynamic allocation; we already have a "buffer" type which is just a block of bytes, that could be used for a basic memory allocation block.
- There would need to be intrinsics for reading and writing typed objects in and out of buffers, but that's a trivial piece of language code to implement.

So ultimately, I'm considering having no bare pointers at all, and dynamic management is composed of RAII semantics plus typesafe reads/writes in and out of buffer blocks.


Not 100% sold on this approach just yet, but it seems to be promising.

Share this post


Link to post
Share on other sites

So ultimately, I'm considering having no bare pointers at all, and dynamic management is composed of RAII semantics plus typesafe reads/writes in and out of buffer blocks.

Is that going to provide you with enough primitives to build automatic ref counting?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement