In other languages I’ve seen single map performing better than a map of maps or even map of maps of maps. I wanted to test which way is better in Go. So I wrote this test:

package main

import (
	"fmt"
	"testing"
)

type structID struct {
	first  int
	second int
	third  int
}

var Blackholemapmapmap map[int]map[int]map[int]string
var Blackholemapstruct map[structID]string

func BenchmarkName(b *testing.B) {
	b.Run("mapmapmap def maps", func(b *testing.B) {
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			m := map[int]map[int]map[int]string{}
			test(func(first, second, third int, value string) {
				seconds := m[first]
				if seconds == nil {
					seconds = map[int]map[int]string{}
					m[first] = seconds
				}
				thirds := seconds[second]
				if thirds == nil {
					thirds = map[int]string{}
					seconds[second] = thirds
				}
				thirds[third] = value
			})
			Blackholemapmapmap = m
		}
	})
	b.Run("mapstruct def map", func(b *testing.B) {
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			m := map[structID]string{}
			test(func(first, second, third int, value string) {
				m[structID{
					first:  first,
					second: second,
					third:  third,
				}] = value
			})
			Blackholemapstruct = m
		}
	})
	b.Run("mapmapmap exact maps", func(b *testing.B) {
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			m := make(map[int]map[int]map[int]string, firstsCount)
			test(func(first, second, third int, value string) {
				seconds := m[first]
				if seconds == nil {
					seconds = make(map[int]map[int]string, secondsCount)
					m[first] = seconds
				}
				thirds := seconds[second]
				if thirds == nil {
					thirds = make(map[int]string, thirdsCount)
					seconds[second] = thirds
				}
				thirds[third] = value
			})
			Blackholemapmapmap = m
		}
	})
	b.Run("mapstruct exact map", func(b *testing.B) {
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			m := make(map[structID]string, firstsCount*secondsCount*thirdsCount)
			test(func(first, second, third int, value string) {
				m[structID{
					first:  first,
					second: second,
					third:  third,
				}] = value
			})
			Blackholemapstruct = m
		}
	})
}

const (
	firstsCount  = 100
	secondsCount = 10
	thirdsCount  = 10
)

func test(add func(first, second, third int, value string)) {
	for first := 0; first < firstsCount; first++ {
		for second := 0; second < secondsCount; second++ {
			for third := 0; third < thirdsCount; third++ {
				add(first, second, third, fmt.Sprintf("%d-%d-%d", first, second, third))
			}
		}
	}
}

Running it I got these results.

BenchmarkName
BenchmarkName/mapmapmap_def_maps
BenchmarkName/mapmapmap_def_maps-10     634	  1787864 ns/op	 812567 B/op	13332 allocs/op
BenchmarkName/mapstruct_def_map
BenchmarkName/mapstruct_def_map-10      686	  1723303 ns/op	1606390 B/op	10125 allocs/op
BenchmarkName/mapmapmap_exact_maps
BenchmarkName/mapmapmap_exact_maps-10   750	  1614700 ns/op	 584977 B/op	12225 allocs/op
BenchmarkName/mapstruct_exact_map
BenchmarkName/mapstruct_exact_map-10    892	  1327966 ns/op	 814922 B/op	10003 allocs/op
PASS

It seems to me that there is no clear advantage of on way of writing things over the other. It is clear that specifying the exact size of the map upfront is beneficial, but that is the case with any single map also. Regarding the topic in question - one way we receive slightly better performance and less memory allocations, the other way we receive less memory allocated. What is more important depends on the context.