Нужна стратегия для кеширования информации об объекте без сохранения его активности

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

Хорошей аналогией может быть то, что я визуализирую интерфейс. Моя функция рендеринга возвращает кадр, но 99% работы, которая ушла на рендеринг этого кадра, используется и выбрасывается. Большая часть этой работы может быть использована повторно, если в следующий раз, когда меня попросят визуализировать фрейм, содержащий этот элемент управления, я пойму, что это тот же элемент управления (только, возможно, с немного другим состоянием).


Если бы это была реальная жизнь, и я имел бы дело с людьми, я бы дал каждому «удостоверение личности», чтобы вернуть мне его в следующий раз, когда они придут, чтобы я мог найти их в своей базе данных, если я когда-нибудь их снова увижу. К сожалению, в большинстве языков программирования (включая C#) вы не можете просто «дополнить» произвольные объекты пользовательскими данными, чтобы их было легче идентифицировать позже.

Я подумал о хранении в кеше объектов, которые видел раньше:

Dictionary<Object, ReallyExpensiveInformation> cache = new ...;

Или, если нас беспокоят объекты, которые неправильно переопределяют "Равно":

Dictionary<CachedObject, ReallyExpensiveInformation> cache = new ...;
// Where CachedObject is defined as:
private class CachedObject : IEquatable<CachedObject> {
    Object Value { get; set; }
    // Get the native hash code that is based on the object reference
    override int GetHashCode() => RuntimeHelpers.GetHashCode(Value);
    // Ensure each new reference is processed, even if they would claim to be equivalent
    override bool Equals(CachedObject other) => ReferenceEquals(Value, other.Value);
}

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

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

Мне почти жаль, что не было способа перехватить «счетчик ссылок» объекта и сбросить его, когда я останусь последним.


В любом случае, моей следующей мыслью было бы создать свою собственную фазу "GarbageCollection". Улучшите мой словарь, указав время последнего доступа, и удалите все записи, которые давно не использовались повторно. Однако это сложная проблема, которую нужно решить. Мне интересно, не забыл ли я придумать более простое решение.

Надеюсь, ясно, что я не контролирую обрабатываемые объекты, иначе я бы улучшил их объектную модель.

Какую информацию хотите сохранить? Я почти хочу сказать: разорвите объект с помощью отражения, получите то, что вам нужно, а затем используйте HashCode в качестве ключа.

TyCobb 13.06.2018 00:11
docs.microsoft.com/en-us/dotnet/api/… Подходит для кеширования. Если он собран сборщиком мусора, перезагрузите его, если не используйте.
Eser 13.06.2018 00:15

@Eser Однажды я вспомню, что он существует ... надеюсь, что это тот день, когда он мне понадобится. знак равно

TyCobb 13.06.2018 00:16
WeakReference выглядит невероятно полезным. Дай мне посмотреть, что я могу сказать об этом.
Alain 13.06.2018 00:36

@Eser Я попытался разработать решение на основе этого класса, если у вас есть какие-либо дополнительные предложения или ошибки. Спасибо!

Alain 13.06.2018 01:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
58
1

Ответы 1

Итак, @Eser только что подключил меня к System.WeakReference. Посмотрим, как это будет выглядеть:

private class CachedReference : IEquatable<CachedObject> {
    private readonly int _hash;
    public WeakReference Value { get; }
    public CachedReference (Object obj) {
        Value = new WeakReference(obj);
        _hash = RuntimeHelpers.GetHashCode(Value);
    }
    override int GetHashCode() => _hash;
    // If our value is garbage collected, we'll stop matching anything
    override bool Equals(CachedObject other) =>
        Value != null && ReferenceEquals(Value, other.Value);
}

ConcurrentDictionary<CachedReference, ReallyExpensiveInformation> cache = new ...;

// And we use it like this
public ReallyExpensiveInformation GetOrCompute(Object obj) =>
    return cache.GetOrAdd(new CachedReference(obj), key => ComputeExpensiveInfo(key.Value));

Похоже, нам все еще нужен какой-то процесс, чтобы «собрать мусор» словарные статьи, которые остались висящими:

  • Для экономии памяти, используемой записями словаря, ключами и значениями кеша
  • Чтобы сохранить размер словаря маленьким / и, следовательно, ускорить поиск.

Возможно, что-то вроде этого?

Timer garbageCollection = new Timer { AutoReset = true; Interval = 1000 };
Timer.Elapsed += () => cache.Keys.Where(k => k.Value == null)
                                 .ToList().ForAll(k => cache.TryRemove(k, out _));

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

Определенно лучше, чем мы были, но мне интересно, есть ли что-нибудь более элегантное, что можно сделать с уборкой.

ПамятьКэш вместо ConcurrentDictionary?
Eser 13.06.2018 01:22

Другие вопросы по теме