Наименее недавно используемый кэш с использованием C++
Я пытаюсь реализовать кэш LRU с помощью C++ . Я хотел бы знать, какой лучший дизайн для их реализации. Я знаю, что LRU должен предоставить find (), добавить элемент и удалить элемент. Удаление должно удалить элемент LRU. каковы лучшие ADTs для реализации этого Например: если я использую карту с элементом в качестве значения и счетчика времени в качестве ключа, я могу искать в O(logn) время, вставка-O(n), удаление-O (logn).
7 ответов
одна из основных проблем с кэшами LRU заключается в том, что существует мало операций "const", большинство изменит базовое представление (хотя бы потому, что они ударяют по доступному элементу).
это, конечно, очень неудобно, потому что это означает, что это не традиционный контейнер STL, и поэтому любая идея показа итераторов довольно сложна: когда итератор разыменован, это доступ, который должен изменить список, на котором мы повторяем... подумать только!.
и рассмотрение представлений, и по отоношению к скорости и потреблению памяти.
это неудачно, но вам понадобится какой-то способ организовать ваши данные в очереди (LRU) (с возможностью удаления элементов из середины), и это означает, что ваши элементы должны быть независимыми друг от друга. А std::list
подходит, конечно, но это больше, чем нужно. Здесь достаточно одного связанного списка, так как вам не нужно повторять список назад (вам просто нужна очередь, после все.)
однако одним из основных недостатков этих является их плохая локальность ссылки, если вам нужно больше скорости, вам нужно будет предоставить свой собственный (пул ?) распределитель для узлов, чтобы они держались как можно ближе друг к другу. Это также несколько облегчит фрагментацию кучи.
Далее, Вам, очевидно, нужна структура индекса (для бита кэша). Самое естественное-обратиться к хэш-карте. std::tr1::unordered_map
, std::unordered_map
или boost::unordered_map
обычно хорошее качество реализации, некоторые должны быть доступны для вас. Они также выделяют дополнительные узлы для обработки хэш-столкновений, вы можете предпочесть другие виды хэш-карт, проверьте статья Википедии на эту тему и почитайте о характеристиках различных методов реализации.
продолжая, есть (очевидная) поддержка потоков. Если вам не нужна поддержка потоков, то все в порядке, если вы это делаете, однако, это немного сложнее:
- как я уже сказал, там мало
const
операция на такой структуре, таким образом, вам действительно не нужно дифференцировать доступ для чтения/записи - внутренняя блокировка в порядке, но вы можете обнаружить, что она не играет хорошо с вашим использованием. Проблема с внутренней блокировкой заключается в том, что она не поддерживает концепцию "транзакции", поскольку она отказывается от блокировки между каждым вызовом. Если это ваш случай, преобразуйте свой объект в мьютекс и предоставьте
std::unique_ptr<Lock> lock()
метод (в debug вы можете утверждать, что блокировка взята в точке входа каждого метода) - существует (в стратегиях блокировки) проблема реентранса, то есть возможность "перезапустить" мьютекс из того же потока, проверить Boost.Поток для получения дополнительной информации о различных доступных блокировках и мьютексах
наконец, существует проблема отчетности об ошибках. Поскольку ожидается, что кэш не сможет получить данные, которые вы вставили, я бы рассмотрел использование исключения "плохой вкус". Рассмотрим либо указателей (Value*
) или импульс.Необязательно (boost::optional<Value&>
). Я бы предпочел Boost.Необязательно, потому что его семантика ясна.
лучший способ реализовать LRU-использовать комбинацию std::list и stdext::hash_map (хотите использовать только std, а затем std:: map).
- сохранить данные в списке, так что наименее недавно используемый в последние и используйте карту, чтобы указать на элемент списка.
- для " get " используйте карту, чтобы получить
список addr и извлечение данных
и переместите текущий узел в
сначала(так как это было использовано сейчас) и обновите карту. - для "вставить" удалить последний элемент из списка и добавить новые данные вперед и обновите карту.
Это самый быстрый вы можете получить, если вы используете hash_map вы должны почти все операции, выполняемые в O (1). При использовании std:: map он должен принимать O(logn) во всех случаях.
очень хорошая реализация доступен здесь
этой статьи описывает пару реализаций кэша C++ LRU (одна с использованием STL, одна с использованием boost::bimap
).
когда вы говорите приоритет, я думаю,"кучу" что, естественно, приводит к увеличение-клавиша и удалить-min.
Я бы вообще не сделал кэш видимым для внешнего мира, если бы мог избежать этого. Я бы просто имел коллекцию (чего угодно) и обрабатывал кэширование невидимо, добавляя и удаляя элементы по мере необходимости, но внешний интерфейс был бы точно таким же, как у базовой коллекции.
Что касается реализации, куча, вероятно, наиболее очевидна. Он имеет сложности, примерно похожие на карту, но вместо построения дерева из связанных узлов он упорядочивает элементы в array и "ссылки" неявно основаны на индексах массива. Это увеличивает плотность хранения вашего кэша и улучшает локальность в" реальном " (физическом) кэше процессора.
Я бы пошел с обычной кучей на C++.
с помощью std::make_heap (гарантированного стандартом, чтобы быть O(n)), std::pop_heap и std:: push_heap в #include, реализация будет абсолютно торт. Вам нужно только беспокоиться об увеличении ключа.