:)
Еще пару лет назад я считал, что выделение памяти приложению происходит так, как описано во всех букварях — приложение вызывает malloc() и через него запрашивает у оси нужное кол-во памяти. Потом в нужный момент возвращает через free() (вроде так функция называется). В этой простой схеме malloc — лишь "посредник", мальчик-падаван, ходит в ось за памятью и приносит ее приложению, ну и обратно отдает тоже.
Оказалось, что пытливые умы разработчиков осей уже давным-давно эту схему усложнили — malloc теперь является не просто подаваном, он не хочет больше быть на побегушках. Он организовал свой собственный департамент распоряжения памятью :) и, хоть по-прежнему ходит в ось, получая память, практически перестал ходить в ось, чтобы ее отдать. Он ее себе в заначку складывает ("и вот на эти два прОцента я и живу" (C)). И когда приложению снова нужна память, malloc отдает ее из своих запасов, не обращаясь к оси (если у него конечно есть нужный объем).
Так вот алгоритмы распоряжения этой заначкой — это тот краегоульный камень, об который бьются разработчики сторонних аллокаторов, каждый реализует свои собственные. Там есть масса моментов, которые можно оптимизировать, в частности, фрагментация памяти. Почти как с фрагментацией файлов на диске, проблемы те же. И если у аллокатора в заначке есть, скажем, 1GB памяти, но кусками по 100MB, а приложение запросило 500MB разом, т.е. одним непрерывным куском, то аллокатор говорит "у меня столько нету" и идет в ось попрошайничать. А тот суммарный гиг, что у него порезанный в заначке лежит — он так и лежит. И при неудачном стечении обстоятельств может лежать долго. А при еще более неудачном — может плодиться и размножаться.
Проблема в том, что аллокатор работает в адресном пространстве процесса, который память запрашивает. В нашем случае — вапора. Т.е. вся память, которую аллокатор получил у оси (и та, что он приложению отдал, и та, что в заначке лежит) — она считается выделенной процессу!
Т.е. если другими словами сказать, то в рамках выполняемого нашего процесса (вапор) сидит присосавшийся к нему спекулянт памятью и мутит свои делишки. С точки зрения оси, он — часть нашего процесса. С точки зрения процесса, он — часть оси.
Вроде 50/50, если по-справедливости, но когда и что в нашем мире бывает по-справедливости? В нашем случае рулит не справедливость, а главенство — и ось тут главная. Она видит 8 гигов памяти, которые она выделила процессу (при этом 7 из них лежат в заначке у аллокатора, мелко покрошенные, а сам вапор использует 1 гиг) и говорит — а не охренел ли ты, дружок? И насылает на процесс ООМ-киллера.
Дальше всё хорошо — рестарт, обнуление, очередной "зубец" на графиках потребления памяти.
Так и живем.
Отсюда становится понятным, что качество алгоритмов аллокатора, управляющего нашей памятью (т.е. памятью, которая была осью выделена нашему процессу) напрямую влияет на наш процесс, ничуть не меньше, чем качество того кода, который мы сами написали.
И как следствие — есть прямой интерес выбрать и использовать хороший аллокатор. Оси от этого ни холодно, ни жарко, это влияет только на нас, ей плевать, а вот нам не плевать, это же наш процесс будет крашиться.
Получается, что раз нельзя отказаться от этого присосавшегося спекулянта (таковы правила игры в современных осях), то можно и нужно хотя бы заменить его на более лучший. Тем более, что делается это легко и просто, и есть, из чего выбрать.
Ну вот в Debian 12 выбрали дефолтным почему-то не jemalloc, а mimalloc, так что я после апгрэйда системы jemalloc для вапора отключил, посмотрим, как оно будет. Про mimalloc отзывы и до этого были хорошие. Есть еще гугловский, tmalloc или как-то так, не помню точное название.
вот этот образ swift собран на базе ubuntu. Не знаю какой там дефолтный..
Обсуждают сегодня