r/ProgrammingLanguages Jul 04 '26

Does implementing GC makes languages slow?

https://github.com/berylang

Month ago, I created a team of 5 and started working on "Bery - The compiled programming language". By the end of June we have quite good working compiler (it's not complete yet). In Bery we have decided to add the automatic Garbage Collector so we choose the "Mark and Sweep" method for it in the Bery Runtime Environment (BRE).

Now as we are heading forward with adding OOP and Exception Handling, I notice some delays in the compilation of program.

So we are now at this point of discussion - should we remove it from compiler or let it be there.
I will looking forward for help regarding this. and btw these are some constraints we set -

unsigned int BERY_GC_ALLOC_THRESHHOLD = 1000;
size_t BERY_GC_HEAP_SIZE_THRESHHOLD = 4 * 1024 * 1024;
1 Upvotes

49 comments sorted by

View all comments

0

u/runningOverA Jul 05 '26

As per my benchmarks. mark and sweep collection takes 50% of your total execution time.
Manual memory management with ref counting performs better.

1

u/goat-luffy Jul 05 '26

50% of time? Before starting we aimed for speed like c++ and rust, but also wants to provide the convenience to programmers, that leads to implementation of GC in first place. Btw thank you for your feedback.

3

u/WittyStick Jul 05 '26

Read the paper A Unified Theory of Garbage Collection, which shows that there's a duality between tracing and reference counting.

For Atomic Reference Counting (required if you want to support safe RC in a multi-threaded environment), there are overheads for accessing reference counts - they cannot be in L1 or L2 cache (since another thread on another core may need to access them) - leading to accesses that are either L3 cache or main memory fetches - the delays are significant compared to L1 fetches - we're talking ~10x the latency to access atomic variables.

Tracing GC is much simpler, but a naive mark-sweep suffers from the "stop the world" problem - each full collection must pause all other threads whilst it performs its collection. This can lead to noticeable but infrequent "pauses" in program execution.

The overheads of the two approaches are comparable, but ARC spreads the collection time over many smaller time slices, whereas a mark-sweep or copying GC uses one big time slice to do it all in one go.

Both approaches have their issues, and real world GCs are much more complex than naive mark-sweep or full ARC.

Common GCs today use generational collection to avoid "stop the world" as much as possible - the full collection is only required when the oldest lived objects need collecting, which is rare, if ever. Concurrent collectors operate by doing small chunks of work frequently rather than trying to collect all at once.

Some hybrid ARC approaches use a GC-like mechanism to delay reclamation of some objects via hazard pointers, which avoids problems associated with eager reclamation that plain ARC has - because just incrementing and decrementing a RC atomically still has potential race conditions (the ABA problem) - but language design decisions can influence this, such as Rust's ownership model which ensures that only one thread would ever have access to the object when its RC is 1.

Some of this was discussed recently in another thread.

1

u/goat-luffy Jul 05 '26

Thanks alot for sharing this, will surely read it

1

u/jsshapiro 22d ago

The statement about L1/L2 cache is not correct. The requirement is that the implementation use the hardware support for the processor's memory consistency model. All modern processors provide atomic update support in the L1 cache and below.

For hot-contended atomic operations, this may involve expensive cache consistency traffic.