Art of immutability and garbage collection

Explore the world of immutability through a conversation between two developers, Han and Sean.

CaratLane Insider
CaratLane Insider

--

By Tushargoel S“Creating value with code”

In this blog post, We’ll discuss why immutability matters, its benefits, and how it affects memory usage in everyday coding.

Han starts by expressing his interest in JavaScript and its quirks. Sean introduces him to functional programming and the idea of keeping data unchanged, even in a language that doesn’t enforce it.

Han: Why immutability is essential when JavaScript doesn’t make it a default? No language makes immutability out of the box. Why should you care, if the language doesn’t care?

He questions its practicality and asks how it can be useful.

Sean: Because the Trust matters. If you asked to borrow my car for travel and I lent it to you, it would be expected that you return the car as it is. If, instead, you convert it into a bike and return it, it wouldn’t align with the initial request

Sean explains the importance of trust and the problems that can arise with mutable (changeable) data. He talks about some issues:-

Unpredictable State: When different parts of the code share the same data, changes made in one place can affect other parts unexpectedly. This leads to a tight connection between code fragments, making it hard to debug issues hence leaving the system in an uncertain state hence makes rollback difficult

Race Conditions: Imagine if two actions try to change the same thing at the same time. This can lead to unpredictable results. For example, if a user saves their profile while another save is still happening, things can go wrong.

Han: It’s not there by default. How are we doing it?

Sean: Yeah, please consider some of my tips to achieve immutability.

He mentioned that achieving immutability is possible through certain rules and principles, such as VOP (Value-Oriented Programming). He also shares simple ways to make objects unchangeable, like freezing them or creating copies. He introduces the idea of using libraries like Immutable.js and more which are based on creating new instances without copying them.

Han: Sounds good, Sean, but what about memory? If we create new values every time, won’t it affect memory usage?

Sean: Hmm, being a high-level language JavaScript did raise questions about memory management. Let’s dive into how Garbage collection works.

Memory management operates on the principles of allocating memory, using it and releasing it when no longer needed. Variables stored in the stack reference objects stored in the heap.

The basic GC algorithm uses references to decide lifespan. It labels an object as garbage if it has zero incoming references. Circular references can cause problems and potential memory leaks.

A more advanced method, the mark-and-sweep algorithm, changes the approach from “not being referenced” to “not being reachable.” It begins with a set of objects known as roots and marks all objects referenced from these roots. This process repeats until all objects not reachable from the roots are labeled as garbage and removed. It solves the biggest problem of the previous algorithm, which was circular references.

Now, applying the Mark-and-Sweep GC to Immutable Objects offers advantages:

These objects, which can be changed (mutable), undergo garbage collection to remove unused ones. Garbage collection marks objects with a tick on it. So, it marks a and b.

The main program decides to swap the contents of objects a and c.

When GC continues, it marks c but stops marking b because it was already marked. So, it thinks b is still in use. During the cleanup, it mistakenly leaves object b behind, and this oversight carries over to the next iteration. This can easily be avoided with immutability in place.

Reduced Tracking Complexity: Immutable objects avoid circular references and have constant references, simplifying tracking throughout their lifetime.

Generational GC Benefits: Many modern garbage collectors, like Generational Garbage Collection, divide the heap into generations, prioritising the younger generation for faster collection. This benefits mutable objects that survive longer due to ongoing modifications. Refer this https://recepinanc.medium.com/til-12-garbage-collection-young-vs-old-generations-ab95b6a68653 to understand the importance of immutability in modern GC.

Han: GC Stuff — Does it Matter?

Han, curious but cautious, brings up memory concerns again. He questions why new objects are constantly created, especially when managing things like a React state. This time he also shares an example to justify his statement.

He said If there are 10,000 tasks, each using around 140 bytes, an array holding 1.4 MB the total memory allocation can be significant (approximately 7 GB). This is due to the creation of new objects for each task, adhering to the principles of immutability instead of creating new objects for each task, as stated above one could keep a local array in ref and mutate it if it has a local scope which could have saved a lot of unnecessary usage.

Sean: Making Sense of Memory but I don’t agree with Immutability being the culprit

Sean responds by introducing some concepts achievable by libraries like immer.js to neutralize the impact of immutability on memory. He also mentioned some ways being used:-

Persistent Data Structure: Creates new versions of a data structure while keeping the old ones intact. This allows sharing of memory between versions, reducing the need for duplicating entire structures.

Structural Sharing: Shares data between different versions by referencing rather than copying the entire dataset. It’s akin to how Git manages multiple versions of source code.

The decision to use a local array or a state hook in React depends on the state management strategy you are following. Also, he added how performance depends on our practices. If I ask you how quickly you can add 10,000 integers to an array in 1000 iterations while following the principles of immutability, what would your approach be?

The array allocation method achieves the fastest speed at 46,231 operations per second, while using the spread operator is the slowest, clocking in at 40.13 operations per second.

Conclusion

In summary, determining whether a well-coded system will use more memory with mutable or immutable collections is not always straightforward. Indeed, there are instances where using extra memory can be more beneficial than spending time debugging issues or vice versa. The answer depends on the specific usage context, the nature of the data, the specific requirements, and the trade-offs between defensive copying and memory usage from immutability can vary.

Achieving a balance between memory consumption and performance is essential. Additionally, JavaScript runtimes allow tuning or configuring garbage collection behavior, impacting optimization. Understanding the employed GC strategy and potential tuning options is crucial for optimal performance.

Meet The Team!

Author

Tushargoel S

Reviewer

Robin Malhotra

Editor

Ramyaraghavan R

We at CaratLane are solving some of the most intriguing challenges to make our mark in the relatively uncharted omnichannel jewelry industry. If you are interested in tackling such obstacles, feel free to drop your updated resume/CV to careers@caratlane.com

--

--