Published on

JavaScript Performance Optimization - Part 2

Authors

The following are notes from the Frontend Masters - Blazingly Fast JavaScript course by the great ThePrimeagen. I've also included my own personal insights and discoveries from research and practical experience.

In the second part, we dive into specific strategies and optimizations.



Usual Suspects for Optimization

Map and Set Are Not Always the Right Answer

While Map and Set are useful for certain tasks, they aren't always the most efficient choice.

When to Use Them

  • Performance: They excel at operations like adding, removing, and checking for the existence of elements.
  • Flexibility: Map supports keys of any type, and Set ensures unique values.

When They Fall Short

  • Memory Overhead: Using these structures unnecessarily can lead to higher memory consumption.
  • Simple Cases: For small-scale data operations, arrays and plain objects might be faster due to their simplicity.

Understanding Garbage Collection (GC)

JavaScript manages memory automatically via garbage collection, but understanding how it works can help you write more efficient code.

Major vs. Minor GC

  1. Major GC: Walks through all objects, marking and sweeping those no longer in use. This process can be slow.
  2. Minor GC: Targets short-lived objects, using a faster collection method.

Going Further

There is a great blog on the GC by the v8 (chrome javascript engine) developers which is recommended reading to get some insight into the internal memory management of JavaScript.

You will also find a youtube video link with a presentation by one of the GC developers which is also great learning in case you prefer that over reading.

Other good videos related to this:

Practical Tips

  • Avoid Excessive Object Creation: Minimize unnecessary object allocations, especially in loops or frequently called functions.
  • Memory Pools: Use memory pooling to reduce the load on the garbage collector.

Memory Pools and Object Reuse

Memory Pooling

This technique reuses objects to minimize garbage collection overhead. Here's a simple implementation:

tsx
export class Pool<T> {
  private pool: T[];

  constructor(private ctor: () => T) {
    this.pool = [];
  }

  public get(): T {
    return this.pool.length > 0 ? this.pool.pop()! : this.ctor();
  }

  public set(t: T) {
    this.pool.push(t);
  }
}

Advantages

  • Reduced GC Load: Less frequent garbage collection.
  • Faster Execution: Reusing objects is quicker than creating new ones.

Considerations

  • Memory Overhead: Pools can use more memory if not carefully managed.
  • Object Lifecycle Management: Ensure objects are properly reset and reused.

Promises and Async/Await

Performance Impact

Promises introduce overhead, which can add up in performance-critical code. Consider alternatives like raw callbacks in extreme cases.

Optimization Techniques

  • Use Promise.all for parallel execution when possible.
  • Implement throttling to control the number of concurrent promises.

The Best JavaScript is No JavaScript

Whenever possible, minimize your JavaScript footprint. Use libraries like uWebSockets.js, which leverage C++ for performance-critical tasks.

Example: uWebSockets.js

This library outperforms traditional Node.js servers like Fastify by implementing core features in C++.

Final Thoughts

Performance optimization is an ongoing process. Use the techniques discussed to analyze and improve your JavaScript code, but remember to balance performance with maintainability. Measure, optimize, and iterate!