Как освободить память, выделенную для какой-либо структуры в Crystal - вручную?
У меня есть основанный на Kemal веб-сервис RESTful, который возвращает "очень большие" (размером от 10 до 17 МБ) порции данных JSON, которые создаются методом to_json из "большой" структуры Hash.
Согласно предупреждающим сообщениям GC, мой код "может привести к утечкам памяти", а мои собственные измерения показывают, что память "протекает" во время выполнения приложения.
Так что, я думаю, было бы хорошо освободить память, выделенную для хэша, и ее представление в виде строки JSON вручную, но я не знаю, как это сделать: мои эксперименты с плохо документированным методом GC.free не увенчались успехом, и я не не знаю, в каком направлении продолжать мои расследования...
Пожалуйста, скажите мне, что я могу сделать, чтобы избежать утечек памяти?
Вы можете посмотреть не очень свежую, но в целом актуальную версию моего очень простого приложения (на самом деле оно было разработано в закрытом сегменте корпоративной сети) здесь https://github.com/DRVTiny/Druid/blob/master/src/druid_mp.cr
Код, который приводит к утечкам памяти:
get "/service/:serviceid" do |env|
if (svcid = env.params.url["serviceid"]) && svcid.is_a?(String) && svcid =~ /^s?\d+$/
druid.svc_branch_get((svcid[0] == 's' ? svcid[1..-1] : svcid).to_i).to_json
else
halt env, status_code: 404, response: %q({"error": "Wrong service identificator"})
end
rescue ex
halt env, status_code: 503, response: {"error": "Unhandled exception #{ex.message}"}.to_json
end
PS Я вставил ловушку after_all, выполняющую GC.collect после каждого запроса пользователя. Не знаю, может быть, это может решить мою проблему (но я думаю, что это совсем не так).
UPD: После того, как я добавил GC.collect в ловушку after_all Kemal - утечки памяти исчезают. Но глобальный GC.collect, вероятно, слишком медленный и, как я знаю, он блокирует все волокна и socket.accept(). Пожалуйста, дайте мне знать, если я ошибаюсь.
1 ответ
Да не стоит звонить GC.collect
после каждого запроса.
Помимо улучшений в GC (которые будут в конечном итоге), самый простой способ - избежать бесполезного выделения строк. Судя по вашему примеру кода, вам не нужен результат из to_json
вызов в память в виде строки. Вы можете просто сериализовать его прямо в поток ввода-вывода, как to_json(env.response)
, Это быстрее всего и не выделяет дополнительную память, полностью избегая проблемы с освобождением памяти.