Мы наблюдаем большой объем памяти в нашей программе, когда она демаршаллирует JSON ответ, содержащий большой фрагмент (например, один миллион UUID).
Я попытался смоделировать это с помощью некоторого простого теста, программа вызовет API-интерфейс на какой-то фиктивный сервер, который вернет 1M UUID в качестве ответа, а затем демаршалирует ответ в структуру. Фрагмент кода:
type response struct {
ItemID []string
}
func main() {
benchRes := testing.Benchmark(func(b *testing.B) {
for n := 0; n < b.N; n++ {
resp, err := http.Get("http://localhost:8080")
if err != nil {
fmt.Println(err)
}
body, _ := ioutil.ReadAll(resp.Body)
res := response{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Println(err)
}
resp.Body.Close()
}
})
fmt.Println(benchRes)
fmt.Println(benchRes.MemString())
}
Результат:
2 532327650 ns/op
232710540 B/op 1000147 allocs/op
Что более или менее подтверждает наше предыдущее наблюдение, то есть большой объем памяти (~ 200 МБ) только для обработки 1M UUID, который должен быть около 40 МБ (?)
Одно предположение, которое я могу придумать, связано с несколькими выделениями памяти для увеличения емкости среза (?).
Проблема усугубляется тем фактом, что память не похоже, со временем восстанавливается ОС, т.е. RSS продолжает увеличиваться с течением времени, и программа через некоторое время убивает OOM. Мы понимаем, что это может быть связано с тем, как работает Go G C в сочетании с используемым флагом MADV_FREE.
Поэтому теперь мы пытаемся выяснить, есть ли способ хотя бы уменьшить объем памяти Go JSON Unmarsal для нашего варианта использования. Спасибо!
Обновление: Пытался декодировать токен за токеном, memAllo c уменьшено с 238 МБ до 176 МБ
65.50MB 41.52% 41.52% 80.50MB 51.03% encoding/json.(*scanner).error
46MB 29.16% 70.68% 46MB 29.16% encoding/json.(*decodeState).literalStore
31.27MB 19.82% 90.49% 157.77MB 100% main.main.func2
15MB 9.51% 100% 15MB 9.51% encoding/json.quoteChar
Похоже на https://github.com/golang/go/issues/10335