WeakReference делает хороший кеш? - PullRequest
14 голосов
/ 30 мая 2009

У меня есть кеш, который использует WeakReferences для кешированных объектов, чтобы они автоматически удалялись из кеша в случае нехватки памяти. Моя проблема в том, что кэшированные объекты собираются очень скоро после того, как они были сохранены в кеше. Кэш работает в 64-битном приложении, и, несмотря на то, что доступно более 4 Гб памяти, все кэшированные объекты собираются (обычно они хранятся в G2-куче в этот момент). Как показывает проводник процесса, сборка мусора вручную не производится.

Какие методы я могу применить, чтобы объекты жили немного дольше?

Ответы [ 7 ]

14 голосов
/ 30 мая 2009

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

Однако, если вашему кешу требуется какая-либо возможность воскрешения, полезно использовать WeakReferences для элементов, ожидающих очистки. Когда элемент соответствует критериям выселения, а не немедленно выселяет его, вы меняете его ссылку на слабую ссылку. Если что-то запрашивает это до того, как оно будет скопировано с помощью GC, вы восстанавливаете его сильную ссылку, и объект может снова жить. Я нашел это полезным для некоторых кешей, которые трудно предсказать паттерны скорости попадания с достаточно частыми «воскрешениями», чтобы быть полезными.

Если у вас есть предсказуемые паттерны успешности, я бы отказался от опции WeakReference и выполнил явные выселения.

5 голосов
/ 30 октября 2012

Существует одна ситуация, когда кэш на основе WeakReference может быть хорош: когда полезность элемента в классе определяется наличием ссылки на него. В такой ситуации может быть полезен слабый интернирующий кеш. Например, если бы у кого-то было приложение, которое десериализовало бы много больших неизменяемых объектов, многие из которых, как ожидали, были дубликатами, и им пришлось бы выполнять много сравнений между ними. Если X и Y являются ссылками на некоторый неизменный тип класса, тестирование X.Equals(Y) будет очень быстрым, если обе переменные указывают на один и тот же экземпляр, но может быть очень медленным, если они указывают на разные экземпляры, которые оказываются равными. Если десериализованный объект совпадает с другим объектом, ссылка на который уже существует, выбор из словаря ссылки на этот последний объект (требующий одного медленного сравнения) может ускорить будущие сравнения. С другой стороны, если бы он соответствовал элементу в словаре, но словарь был ссылкой only на этот элемент, было бы небольшим преимуществом использовать объект словаря вместо простого сохранения объекта, который был прочитан в ; Вероятно, недостаточно преимуществ, чтобы оправдать стоимость сравнения. Для интернирующего кеша было бы хорошо, если бы WeakReferences был признан недействительным как можно скорее, если не существует никаких других ссылок на объект.

5 голосов
/ 30 мая 2009

В .net WeakReference вообще не считается ссылкой с точки зрения GC, поэтому любой объект, имеющий только слабые ссылки, будет собран при следующем запуске GC (для соответствующего поколения).

Это делает слабую ссылку совершенно неуместной для кеширования - как показывает ваш опыт.

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

3 голосов
/ 30 мая 2009

Нет, WeakReference не годится для этого, потому что поведение сборщика мусора может и будет меняться со временем, и ваш кэш не должен полагаться на сегодняшнее поведение. Также многие факторы, находящиеся вне вашего контроля, могут влиять на нагрузку на память.

Существует много реализаций кэша для .NET. Вы можете найти, вероятно, дюжину на CodePlex. Я думаю, что вам нужно добавить к нему что-то, что смотрит на текущий рабочий набор приложения, чтобы использовать его как триггер для очистки.

Еще одно замечание о том, почему ваши объекты собираются так часто. GC очень агрессивен при очистке объектов Gen0. Если ваши объекты очень недолговечны (до тех пор, пока единственная ссылка на них не является слабой ссылкой), то сборщик мусора делает то, для чего предназначен, путем очистки как можно быстрее.

1 голос
/ 16 сентября 2013

Я полагаю, что проблема, с которой вы столкнулись, заключается в том, что сборщик мусора удаляет объекты со слабыми ссылками в ответ не только в ответ на нехватку памяти - вместо этого он будет выполнять сбор данных довольно агрессивно, иногда просто потому, что система времени выполнения думает, что некоторые объекты, вероятно, стали недоступными.

Возможно, вам лучше использовать, например, System.Runtime.Caching.MemoryCache, который можно настроить с помощью ограничения памяти или пользовательских политик вытеснения для элементов.

0 голосов
/ 21 февраля 2015

Немного поздно, но вот соответствующий пример использования:

Мне нужно кэшировать два типа объектов: большие (десериализованные) файлы данных, загрузка которых занимает 10 минут, и стоит 15 ГБ оперативной памяти каждый, и меньшие (динамически скомпилированные) объекты, которые содержат внутренние ссылки на эти файлы данных (меньшие объекты также кэшируются, потому что они генерируют ~ 10 с). Эти кэши скрыты на фабриках, поставляющих объекты (первый компонент не знает о последних), и имеют разные политики выселения.

Когда мой кэш `data file 'высвобождает объект, он заменяет его слабой ссылкой, поэтому, если этот объект все еще доступен при следующем запросе, мы можем воскресить его (и возобновить тайм-аут его кэша). Таким образом, мы избегаем потери (или случайного дублирования) любого объекта до того, как он действительно перестанет существовать (то есть не используется где-либо еще). Обратите внимание, что ни один из кешей не обязан знать о другом, и что никакие другие клиентские объекты не должны знать, что вообще существуют какие-либо кеши (например: нам не нужны области «keepalive», обратные вызовы, регистрация, получение и возврат). и т. д. - все становится намного проще).

Таким образом, хотя использование WeakReference само по себе (вместо кэша) является ужасной идеей (поскольку современные GC обычно настроены на размер кэша ЦП L2, и обычный код будет проходить через это много раз в минуту), это очень полезен как способ скрыть ваших кешей от остальной части вашего кода.

0 голосов
/ 15 мая 2013

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

Если, однако, вам нужен кеш, который выдерживает такую ​​жестокость от GC, вам необходимо использовать или имитировать функциональность, предоставляемую пространством имен System.Runtime.Caching. Имейте в виду, что вам понадобится дополнительный поток, который очищает кеш, когда использование памяти пересекает ваши пороги.

...