Сборка мусора и запоминание функции строка-строка - PullRequest
2 голосов
/ 11 июля 2019

Следующее упражнение взято из с. 234 Программирование Иерусалимского в Lua (4-е издание). ( NB: Ранее в книге автор явно отвергает слово памятка и настаивает на использовании вместо этого запоминание . Имейте это в виду, читая отрывок ниже.)

Упражнение 23.3: Представьте, что вам нужно реализовать таблицу запоминания для функции от строк к строкам. Создание слабой таблицы не приведет к удалению записей, поскольку слабые таблицы не рассматривают строки как собираемые объекты. Как вы можете реализовать запоминание в этом случае?

Я в тупике!

Часть моей проблемы заключается в том, что я не смог придумать способ вызвать (мусорный) сбор строки.

В отличие от таблицы, я могу оснастить ее финализатором, который сообщит, когда таблица собирается быть собранной. Есть ли способ подтвердить, что данная строка (и только эта строка) была собрана сборщиком мусора?


Другая сложность заключается в простом определении спецификации требуемой функции . Лучшее, что я могу сделать, это выяснить, что это не так. Ранее в книге (стр. 225) автор привел следующий пример функции «запоминания»:

Представьте себе универсальный сервер, который принимает запросы в виде строк с кодом Lua. Каждый раз, когда он получает запрос, он запускает load в строке и затем вызывает результирующую функцию. Однако load - дорогостоящая функция, и некоторые команды серверу могут быть довольно частыми. Вместо многократного вызова load каждый раз, когда он получает общую команду, такую ​​как "closeconnection()", сервер может запомнить результаты из load, используя вспомогательную таблицу. Перед вызовом load сервер проверяет в таблице, имеет ли данная строка уже перевод. Если он не может найти соответствие, то (и только тогда) сервер вызывает load и сохраняет результат в таблице. Мы можем упаковать это поведение в новую функцию:

[стандартная памятка (пропущенная) реализация опущена; см. вариант с использованием таблицы слабых значений ниже]

Экономия при такой схеме может быть огромной. Однако это также может привести к непредвиденным отходам. Хотя некоторые команды повторяются снова и снова, многие другие команды выполняются только один раз. Постепенно таблица [«запоминания»] results накапливает все команды, которые сервер когда-либо получал, плюс их соответствующие коды; через некоторое время это приведет к исчерпанию памяти сервера.

Слабая таблица обеспечивает простое решение этой проблемы. Если таблица results имеет слабые значения, каждый цикл сбора мусора удаляет все неиспользуемые в данный момент переводы (что означает практически все из них) 1 :

local results = {}
setmetatable(results, {__mode = "v"})  -- make values weak
function mem_loadstring (s)
  local res = results[s]
  if res == nil then                   -- results not available?
    res = assert(load(s))              -- compute new results
    result[s] = res                    -- save for later reuse
  end
  return res
end

Как отмечается в исходном заявлении о проблеме, эта схема не будет работать, когда функция, подлежащая запоминанию, возвращает строки, потому что сборщик мусора не рассматривает строки как "коллекционные".


Конечно, если разрешено изменять интерфейс нужной функции, чтобы вместо возврата строки она возвращала одноэлементную таблицу, единственным элементом которой является строка результата real , тогда проблема становится почти тривиальной , но мне трудно поверить, что автор имел в виду такое грубое «решение» 2 .

В случае, если это имеет значение, я использую Lua 5.3.


1 Кроме того, если обоснование для памятки (r) заключается в том, чтобы избегать вызова load чаще, чем необходимо, схема, предложенная автором, не имеет смысла для меня , Мне кажется, что эта схема основана на предположении (на самом деле эвристическом), что перевод, который часто используется и, следовательно, платит за памятку (r) ize, также всегда доступен (и, следовательно, не подлежит сбору) , Я не понимаю, почему это обязательно или даже может иметь место.

2 На эту свинью можно нанести помаду в форме __tostring метода, который позволил бы использовать таблицу (тот, который был возвращен функцией memo (r) ized)маскироваться под строку в определенных контекстах;это все еще свинья.

1 Ответ

2 голосов
/ 11 июля 2019

Ваша идея верна: обернуть строку в таблицу (потому что таблица является коллекционной).

function memormoize (func_from_string_to_string)
   local cached = {}
   setmetatable(cached, {__mode = "v"}) 
   return 
      function(s)
         local c = cached[s] or {func_from_string_to_string(s)} 
         cached[s] = c                    
         return c[1]
      end
end

И я не вижу свиней в этом решении: -)

одинэто всегда достижимо (и, следовательно, не может быть собрано).Я не понимаю, почему это обязательно или даже может быть так.

В слабой таблице не будет «всегда доступных» предметов.
Но наиболее часто встречающиеся предметы будутпересчитывается только один раз за цикл GC.
Идеальное решение (никогда не собирать часто используемые элементы) потребовало бы более сложной реализации.
Например, вы можете перемещать элементы из обычного кэша в слабый кэш, когда «таймер неактивности» элемента достигает некоторогопорог.

...