liveBook · Manning

Merge sort

As explained in 100 Go Mistakes and How to Avoid Them

A comparison-sorting algorithm that recursively divides a collection and merges sorted subcollections.

A common mistake is assuming that parallel merge sort is automatically faster. Merge sort repeatedly splits a collection into two sublists, recursively sorts each sublist, and merges the sorted results. Its independent recursive workloads make concurrency possible, but goroutine and synchronization overhead can outweigh the benefit. section 8.2.2

Figure 8.7 Applying the merge sort algorithm repeatedly breaks each list into two sublists. Then the algorithm uses a merge operation such that the resulting list is sorted.

The split-and-merge structure shows why each recursive half can be processed independently before the final merge. section 8.2.2

func sequentialMergesort(s []int) {
    if len(s) <= 1 {
        return
    }
 
    middle := len(s) / 2
    sequentialMergesort(s[:middle])     #1
    sequentialMergesort(s[middle:])     #2
    merge(s, middle)                    #3
}
 
func merge(s []int, middle int) {
    // ...
}

The sequential implementation recursively sorts both slices, then merges them. section 8.2.2

When parallelism loses

A first parallel implementation starts two goroutines and waits with sync.WaitGroup before merging. On 10,000 elements, it benchmarks at 17,525,998,709 ns/op versus 2,278,993,555 ns/op sequentially. A practical fix is a cutoff: use sequential merge sort for slices of 2,048 elements or fewer, and parallelize only larger workloads. section 8.2.2

const max = 2048                      #1
 
func parallelMergesortV2(s []int) {
    if len(s) <= 1 {
        return
    }
 
    if len(s) <= max {
        sequentialMergesort(s)        #2
    } else {                          #3
        middle := len(s) / 2
 
        var wg sync.WaitGroup
        wg.Add(2)
 
        go func() {
            defer wg.Done()
            parallelMergesortV2(s[:middle])
        }()
 
        go func() {
            defer wg.Done()
            parallelMergesortV2(s[middle:])
        }()
 
        wg.Wait()
        merge(s, middle)
    }
}

The cutoff implementation limits goroutine creation while retaining parallel recursion for larger slices. section 8.2.2

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains recursive splitting, merging, goroutine coordination, benchmarking, and a workload-size cutoff.
Examples
Uses sequential and parallel Go implementations, benchmark results, and a 2,048-element cutoff.
What it assumes
Assumes familiarity with Go slices, recursion, goroutines, and sync.WaitGroup.
What you can do after
Evaluate when parallelizing recursive work helps and apply a cutoff to reduce concurrency overhead.
How it connects
Connects merge sort with recursion, goroutines, slices, sync.WaitGroup, and benchmarking.

Also explained in

Compare how 2 books explain merge sort →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.