Skip to content
Go back

JavaScript vs Go performance

JavaScript vs Go performance

JavaScript is the primary language for the web development because it’s wide adoption by all major web browser.

But one of the weaknesses of the JavaScript is it’s single-threaded execution model that can lead to performance bottlenecks, especially in CPU-intensive tasks, as long-running operations can block the main thread and degrade the user experience.

On the other hand concurrency in Go is considered to be one of the major strengths of the language with it’s built-in support for concurrency through goroutines and channels. This is particularly advantageous for building scalable and high-performance systems.

But how big of the difference can it make? Let’s take a look at the following computationally intensive task that can be optimised using multi-threading in Go compared to single-threaded JavaScript.

Let’s consider a simple task of calculating prime numbers within a given range. We’ll create a function to check if a number is prime, and then we’ll iterate over a range of numbers to find all prime numbers within that range. as a baseline:

Here’s a JavaScript example:

function isPrime(num) {
  for (let i = 2, sqrt = Math.sqrt(num); i <= sqrt; i++) {
    if (num % i === 0) return false;
  }
  return num > 1;
}

function findPrimesInRange(start, end) {
  let primes = [];
  for (let i = start; i <= end; i++) {
    if (isPrime(i)) {
      primes.push(i);
    }
  }
  return primes;
}

This code will work fine for smaller ranges but may become slow for larger ranges due to its single-threaded nature.

Now, let’s rewrite the same functionality using Go with the advantage of multi-threading:

package main

import (
    "math"
    "runtime"
    "sync"
)

func isPrime(num int) bool {
    sqrt := int(math.Sqrt(float64(num)))
    for i := 2; i <= sqrt; i++ {
        if num%i == 0 {
            return false
        }
    }
    return num > 1
}

func findPrimesInRange(start, end int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := start; i <= end; i++ {
        if isPrime(i) {
            results <- i
        }
    }
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) // Utilize all CPU cores
    const start, end = 2, 100000000
    numThreads := runtime.NumCPU()
    results := make(chan int, end-start+1)
    var wg sync.WaitGroup

    wg.Add(numThreads)
    chunkSize := (end - start + 1) / numThreads

    for i := 0; i < numThreads; i++ {
        go findPrimesInRange(start+i*chunkSize, start+(i+1)*chunkSize-1, results, &wg)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    var primes []int
    for prime := range results {
        primes = append(primes, prime)
    }
}

Now let’s compare execution times on a modern M1 Mac Pro today:

Here’s the JavaScript code execution time: prime-finder.js

Compared to Go code execution time: prime-finder.go

Conclusion.

Go code executes in 36.33s while JavaScript executes the same task in 51.46s which ~30% slower. That’s not surprising since JavaScript was able to utilise only 99% of CPU power while Go used 605% of CPU meaning it was working in parallel on 6 cores.


Share this post on:

Previous Post
Scrolling less and producing more
Next Post
Holidays and hobbies