Skip to content

Commit

Permalink
Update from 2025-02-01T13:18:03.703077Z
Browse files Browse the repository at this point in the history
  • Loading branch information
svetlyak40wt committed Feb 1, 2025
1 parent 675b1d9 commit cce29ad
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 2 deletions.
4 changes: 2 additions & 2 deletions raw/channel/1002102092834/msg-14.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"message_id": 56,
"message_id": 57,
"from": {
"id": 76226374,
"is_bot": false,
Expand All @@ -9,7 +9,7 @@
"language_code": "en",
"is_premium": true
},
"date": 1738415157,
"date": 1738415646,
"chat": {
"id": 76226374,
"type": "private",
Expand Down
59 changes: 59 additions & 0 deletions ru/posts/pochemu-gc-sbcl-mozhet-14.post
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,65 @@ tg-message-id: 14



Последнее время я работаю над новой версией фреймфорка для создания Telegram ботов. Этот фреймворк использует библиотеку реализующую акторы. И нем обнаружился досадный баг, который я намеревался исправить. Однако в процессе исправления оказалось, что оно может повлиять на производительность. Хорошо что у автора отыскался benchmark с помощью которого можно проверить скорость работы актора.

К моему удивлению этому бенчмарку не хватало 4G памяти для хоть сколько-нибудь длительной работы. Более того, если бенчмарк запустить ненадолго, то оказывалось, что после его работы процесс "толстел" на 2.5G и не отпускал эту память до тех пор, пока не сделаешь вручную `(sb-ext:gc :full t)`.

Это поведение показалось крайне странным. Как вообще можно использовать это язык в production, если он не отпускает память!?

Так я оказался втянут в исследование того, почему garbage collector SBCL не очищает кучу мусора оставшуюся после теста.

Прошло три дня.

После некоторых исследований у меня появилась гипотеза, почему GC не очищает память.

Дело в том, что в бенчмарке N потоков генерят сообщения к одному актору. Если актор не успевает разгребать сообщения, то те накапливаются в очереди. Тест заканчивается, когда все сообщения в очереди обработаны.

Когда в очереди много сообщений и срабатывает GC, то он видит, что на эти сообщения есть ссылки, и не может их подчистить, а потому перекладывает эти объекты в более старшее поколение. И чем дольше разгребается очередь в процессе генерации объектов, тем больше таких объектов оказывается в старших поколениях garbage collector.

Когда тест заканчивается, то ссылок на сообщения уже нет, но из-за того, что GC поместил их в старшие поколения, при регулярных запусках он до этих объектов не добирается и они так и остаются висеть в памяти. А вот `(gc :full t)` их подбирает и подчищает.

Как я это понял? Хотелось бы ответить: "Очень просто!", но нет 🙁

Сначала я решил поисследовать природу объектов, остающихся висеть в памяти после бенчмарка и написал вот такую функциюЖ


```(defun get-random-dynamic-object ()
(let ((count 0))
(sb-vm:map-allocated-objects (lambda (obj type size)
(declare (ignore obj type size))
(incf count))
:dynamic)
(let ((random-idx (random count))
(found-obj nil)
(current-idx 0))
(sb-vm:map-allocated-objects (lambda (obj type size)
(declare (ignore type size))
(when (= current-idx random-idx)
(setf found-obj
(trivial-garbage:make-weak-pointer obj)))
(incf current-idx))
:dynamic)
(values found-obj
random-idx
count))))


```

она достает из памяти случайны объект и возвращает weak указатель на него. Почему weak указатель? Чтобы не возникло лишней ссылки
на объект.

Выяснилось, что значительная часть объектов, это сообщения из очереди актора:


```#<weak pointer: (#<ACTOR path: /user/actor-365, cell: #<ACTOR actor-365, running: NIL, state: NIL, message-box: #<MESSAGE-BOX/BT mesgb-366, processed messages: 8000001, max-queue-size: 0, queue: #<QUEUE-UNBOUNDED {70050E0113}>>>>
NIL NIL)>


```

Далее я попытался выяснить а не держит ли кто ссылки на эти объекты. Для этого в SBCL есть функция поиска корней:



Expand Down

0 comments on commit cce29ad

Please sign in to comment.