Убегание памяти в кучу влечет за собой следующие потенциально решаемые проблемы: Снижение производительности из-за расходов на выделение памяти Снижение производительности из-за расходов на сборку мусора Появление ошибкиOut of Memory , если скорость появления мусора превышает скорость его уборки Указанные проблемы могут решаться несколькими способами: Увеличением объема вычислительных ресурсов (память, процессор) Тонкой настройкой механизма сборщика мусора Минимизацией числа побегов в кучу В данной статье я рассмотрю только третий путь. Если все еще впереди, но уже поставлена цель добиться производительности, близкой к максимально возможной, то нужно знать в лицо главных замедлителей в плане работы с памятью. Встречаем основные конструкции, число которых следует минимизировать:  make , new , map ,go . Есть и более скрытые способы учинить побег, их я рассмотрю уже в процессе "охоты", а пока - основные способы профилактики. Вместо постоянного выделения памяти через make и new следует максимально переиспользовать уже ранее выделенное. Одним из способов добиться такого переиспользования является  sync.Pool(), на habr этот способ был рассмотрен здесь. Чтобы поменьше быть КО замечу, что использовать элементы типа []byte ,как это делается в статье по ссылке, не стоит - при каждом возврате будет дополнительно выделяться 32 байта памяти (для go1.14.6 windows/amd64). Мелочь, но неприятно; если стремиться к совершенству, лучше переиспользовать интерфейсы или указатели, а еще лучше использовать butebufferpool от @valyala. С map история такая. Интенсивное использование map ведет к интенсивному выделению памяти, но это не единственная проблема. Если приложению нужен огромный кэш, и этот кэш реализован через map,  то можем получить то, из-за чего Discord перешел на Rust. Т.е. на постоянное, в рамках уборки мусора, сканирование гигантского скопления указателей будут тратиться ресурсы, и по каким-то метрикам система выйдет за рамки требований. Для решения этой проблемы великий @valyala сделал fastcache, там же можно найти и ссылки на альтернативные решения, и, опять же у него, наряду с другими советами по повышению производительности, можно найти достаточно детальный разбор, как использовать slices вместо  maps. С оператором go просто - все обработку нужно развести по фиксированному набору горутин и каналов. Запускать горутину на каждый запрос можно, но относительно дорого и плохо предсказуемо по расходу памяти. Имеет смысл сделать такое замечание, и я его сделаю - предотвращение массовых "побегов" имеет свою цену, в частности, упомянутый fastcache далеко не "идиоматичен". Нам, например, идеально подходит кэш []byte->[]byteно, не факт, что это так для всех. Возможно, дешевле будет усилить аппаратную часть, а то и вообще ничего не делать - все зависит от требований к системе, те самые "rps", "95th percentile latency"  и т.д. Возможно, и даже скорее всего, все будет работать и в "коробочном" варианте, да еще и с запасом. Так что будет вполне разумным сделать прототип "горячих путей" обработки и погонять на скорость. Т.е. заняться той самой "охотой". Пойдем опять "на поклон" к Александру Валялкину и выполним:  Чтобы начать охоту, необязательно добавлять в тесты на производительность некий профилирующий код, как это предлагается, например, здесь, просто наберем: и Первая команда запустит тесты для стандартного http.Server, вторая - для fasthttp.Server. По выводу мы заметим, что fasthttp примерно в десять раз быстрее и все операции проходят в zero-allocation режиме. Но это не все, теперь у нас есть профили netmem.out и fastmem.out. Смотреть их можно по-разному, для быстрой оценки ситуации я предпочитаю такой способ: Что дает разбивку потребления памяти по 10 самым "прожорливым" функциям: Можно получить подробную схему убеганий в графическом виде через:  После выполнения команды в netmem.svg будет картинка типа такой (фрагмент): Есть и более крутой способ: Здесь, по идее, должен запуститься браузер, и этот браузер с какой-то вероятностью покажет текст:  Could not execute dot; may need to install graphviz. Те,  кто работает на Unix-подобных системах и так знают, что делать, пользователям же Windows могу посоветовать поставить chocolatey а затем, с правами администратора, вызвать cinst graphviz. После этого можно начать по-всякому крутить профиль. Моя любимая крутилка вызывается через VIEW/Source: Здесь, кроме очевидных убеганий через make, мы также видим большие потери на преобразование []byteв string. Операции со строками весьма затратны и, если "идем на рекорд", их следует избегать и работать исключительно с []byte. Еще одним способом "убежать", с которым встречался, является возврат адреса локальной переменной, т.е. return &localVar . Есть и другие варианты, но углубляться не буду - ваш личный профиль их покажет. Несмотря на сокрушительное превосходство fasthttp в этом тесте, именно эту библиотеку я не рекомендовал бы использовать. Или рекомендовал бы с осторожностью - с fasthttp у вас не будет поддержки  HTTP/2.0, поддержка websockets отполирована не с такой тщательностью, как сам fasthttp (на момент, когда я эту тему изучал), ну и, главное, на реальной нагрузке может и не получиться десятикратного выигрыша. У нас в одном тесте на железе типа c5.4xlarge получалось 250.000 RPS для fasthttp.Server  против 190.000 RPS для http.Server . Выигрыш есть, но вам точно надо больше, чем 190.000 RPS? Тут очень многое зависит от профиля нагрузки, от того, что с этой нагрузкой делается дальше, ну и от требований к системе, само собой. Последним моментом, которого хочу коснуться, является сериализация данных. Построив прототип "горячего пути" для своего приложения и погоняв тесты есть шанс увидеть, что самой прожорливой частью являются преобразования из json/yaml в объекты программы и обратно, и на фоне этой прожорливости меркнет все остальное, что бы вы не пытались написать. Выбор решения тут будет весьма нетривиален и его хоть сколько-нибудь полное описание выходит за рамки этой статьи, поэтому ограничусь кратким результатом наших, далеко не репрезентативных, тестов. Выполнив ряд экспериментов, мы остановились на flatbuffers, но сделали вокруг него обертку dynobuffers, назначение которой - читать и писать без генерации кода, используя имена полей. Вещица пока сыровата, но, надеюсь, скоро доделаем. Результаты чтения "все поля большого объекта":  Результаты чтения "несколько полей большого объекта": Последний сценарий является для нас основным, ради которого все и затевалось, и здесь ускорение, по сравнению с тем же linkedin/goavro - весьма и весьма существенное. Опять скажу - все зависит от конкретных данных и способов их обработки. Например, весь выигрыш на (де)сериализации можно потерять при сохранении, ибо avro часто дает "пакует" данные более компактно, чем flatbuffer. С проблемами, которые ведут к снижению производительности в Go, вполне можно бороться, но нужно иметь в виду, что эта борьба имеет свою цену. Прежде, чем начинать ее, лучше экспериментально сопоставить возможности выбранного способа обработки данных с требованиями к системе - может быть, все будет работать прямо "из коробки" с минимальными ухищрениями. Грехи оптимизации производительности Как мы оптимизировали наш DNS-сервер с помощью инструментов GO Профилирование и оптимизация программ на Go Заставка Охота на пиранью