Running Crystal without a GC (a.k.a. leaking memory)

Hi forum,

Recently I’m doing some experiments on manual memory management using Crystal, first I was trying to check how to fit a Rust-like ownership model into the language, then I realize that there’s a faster way to accomplish this without even touch the compiler, i.e. by using a C++ like memory management model (most people stop reading here). Let’s ignore the problems caused by manual memory management and focus on the problem:

How to have a deterministic memory usage on programs written in Crystal that need this for whatever reason?

I found a simple and stupid way to accomplish this with this snippet of code:

{% unless flag?(:gc_none) %}
{%  raise "You want to leak memory or not!? so use -Dgc_none" %}
{% end %}

@[AlwaysInline]
def free(ptr : Pointer) : Nil
  GC.free(ptr.as(Pointer(Void)))
end

@[AlwaysInline]
def free(obj : Reference) : Nil
  obj.finalize
  # pointerof(obj) is a pointer to the local variable obj, but we need the pointer to what it points to.
  free pointerof(obj).as(Pointer(Void*)).value
end

class Array(T)
  def finalize
    free @buffer
  end
end

class String
  def finalize
  end
end

# Now using it:
a = [1, 2] of Int32
free a

a = 42
str = "hey #{a}"
free str

After few small tests with Array and non-literal strings it worked… despite of always leak 8K according with valgrind.

Good things about this approach:

  • Not a different language, everything still Crystal, just more dangerous and probably leaking some memory
  • Can be done in a shard.

Bad things about this approach:

  • Need to monkey patch a lot of classes in stdlib.
  • Temporary objects will leak, e.g. "hey" + "ho" +"leak" + "baby".
  • Trying to do free "string literal" will crash, since the memory is probably in a readonly memory page, causing free call to fail with free(): invalid pointer

So finally the question: Is there a way to know if a string comes from a string literal or if it was built at runtime?

I’m doing all this for fun, so answers like: Use language X, Why you don’t like a GC, etc are useless.

Thanks.

6 Likes

I suppose you could look at the address of a string yourself to determine whether it’s in the program data or not.

However, I don’t think this entire approach is a good idea. Crystal’s entire stdlib is designed for use with a GC. If you want manual memory management, you would need to change a lot in stdlib. IMO a better approach would be to have a completely separate stdlib for non-GC use.

1 Like

Don’t say things like this and give me hope that I might one day program my microcontrolers in a language that is not terrible.

4 Likes

Well you can technically start doing that. Writing programs in Crystal against only libc instead of Crystal’s own stdlib is entirely possible. So you get a not terrible language… but still have to use ugly C bindings.

I haven’t actually tried that yet. Would be a nice experiment.

The compiler allow you to use a different prelude, remove all GC related code and even specify the entry point, a.k.a. main func. So depending on the micro controller constraints I think it’s already possible to do it. However usually micro controller code requires some non-standard C code to deal with their interrupts, etc… anyway, I agree it would be a nice experiment too :slight_smile:

1 Like

Embedded Crystal is indeed possible, but stdlib carries along about 3 MB of baggage for a minimal program. If your CPU can handle multiple threads, the GC will be much less obtrusive than you expect, because it will run in its own thread.

2 Likes

Which is not that bad, actually ;)

You always can do it in C code and then expose it via C bindings.

After thinking more about the experiments I did… all this probably isn’t a good idea, I mean, it works, but the end result is ugly.

Also, Crystal doesn’t support finalize method for structs, making some other things even uglier.