Skip to content

Commit 1095e5e

Browse files
committed
Significantly reduce memory use of maps.Flatten
This patch addresses an issue where maps.Flatten would use an unreasonable amount of memory when being applied to large nested structures. See benchmark comparison: ``` benchmark old ns/op new ns/op delta BenchmarkFlatten-16 9225 3773 -59.10% benchmark old allocs new allocs delta BenchmarkFlatten-16 63 27 -57.14% benchmark old bytes new bytes delta BenchmarkFlatten-16 9029 2405 -73.36% ```
1 parent 7d983f3 commit 1095e5e

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

maps/maps.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111
)
1212

13-
1413
// Flatten takes a map[string]interface{} and traverses it and flattens
1514
// nested children into keys delimited by delim.
1615
//
@@ -28,6 +27,12 @@ func Flatten(m map[string]interface{}, keys []string, delim string) (map[string]
2827
out = make(map[string]interface{})
2928
keyMap = make(map[string][]string)
3029
)
30+
31+
flatten(m, keys, delim, out, keyMap)
32+
return out, keyMap
33+
}
34+
35+
func flatten(m map[string]interface{}, keys []string, delim string, out map[string]interface{}, keyMap map[string][]string) {
3136
for key, val := range m {
3237
// Copy the incoming key paths into a fresh list
3338
// and append the current key in the iteration.
@@ -46,22 +51,13 @@ func Flatten(m map[string]interface{}, keys []string, delim string) (map[string]
4651
}
4752

4853
// It's a nested map. Flatten it recursively.
49-
next, parts := Flatten(cur, kp, delim)
50-
51-
// Copy the resultant key parts and the value maps.
52-
for k, p := range parts {
53-
keyMap[k] = p
54-
}
55-
for k, v := range next {
56-
out[k] = v
57-
}
54+
flatten(cur, kp, delim, out, keyMap)
5855
default:
5956
newKey := strings.Join(kp, delim)
6057
out[newKey] = val
6158
keyMap[newKey] = kp
6259
}
6360
}
64-
return out, keyMap
6561
}
6662

6763
// Unflatten takes a flattened key:value map (non-nested with delimited keys)

maps/maps_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,67 @@ var testMap2 = map[string]interface{}{
3838
"empty": map[string]interface{}{},
3939
}
4040

41+
var testMap3 = map[string]interface{}{
42+
"list": []interface{}{
43+
map[string]interface{}{
44+
"child": map[string]interface{}{
45+
"key": 123,
46+
"child": map[string]interface{}{
47+
"key": 123,
48+
"child": map[string]interface{}{
49+
"key": 123,
50+
"child": map[string]interface{}{
51+
"key": 123,
52+
"child": map[string]interface{}{
53+
"key": 123,
54+
"child": map[string]interface{}{
55+
"key": 123,
56+
"child": map[string]interface{}{
57+
"key": 123,
58+
},
59+
},
60+
},
61+
},
62+
},
63+
},
64+
},
65+
},
66+
map[string]interface{}{
67+
"child": map[string]interface{}{
68+
"key": 123,
69+
"child": map[string]interface{}{
70+
"key": 123,
71+
},
72+
},
73+
},
74+
},
75+
"parent": map[string]interface{}{
76+
"child": map[string]interface{}{
77+
"key": 123,
78+
"child": map[string]interface{}{
79+
"key": 123,
80+
"child": map[string]interface{}{
81+
"key": 123,
82+
"child": map[string]interface{}{
83+
"key": 123,
84+
"child": map[string]interface{}{
85+
"key": 123,
86+
},
87+
},
88+
},
89+
},
90+
},
91+
},
92+
"top": 789,
93+
"child": map[string]interface{}{
94+
"key": 123,
95+
"child": map[string]interface{}{
96+
"key": 123,
97+
},
98+
},
99+
"empty": map[string]interface{}{},
100+
}
101+
41102
const delim = "."
42103

43104
func TestFlatten(t *testing.T) {
@@ -56,6 +117,12 @@ func TestFlatten(t *testing.T) {
56117
}, k)
57118
}
58119

120+
func BenchmarkFlatten(b *testing.B) {
121+
for n := 0; n < b.N; n++ {
122+
Flatten(testMap3, nil, delim)
123+
}
124+
}
125+
59126
func TestUnflatten(t *testing.T) {
60127
m, _ := Flatten(testMap, nil, delim)
61128
um := Unflatten(m, delim)

0 commit comments

Comments
 (0)