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.

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.

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.

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:

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.

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.