|
|
|||||||||||||||||||||||||||||
|
Многопоточная реализация алгоритма кеширования CARTИсточник: habrahabr tridemax
Некоторое время назад передо мной встала задача кеширования запросов в большую базу данных на диске в высоконагруженном многопоточном приложении (С++). Само приложение предназначалось для развертывания в облаке и диск очевидно стал бы самым узким местом. База при этом представляла из себя огромный граф, по которому одновременно ползало множество потоков. Таким образом кеш ко всему еще и должен был быть многопоточным. Идею использования внешних приложений, таких как memcached, я отбросил сразу же - это внесло бы в каждый переход по ребру графа неизбежный дополнительный лаг. Встал вопрос об имплементации внутри приложения. У меня появился выбор - положится на недостаточно оттестированную имплементацию, где починка любого бага стала бы отличным приключением, либо писать свою. Также, беглое изучение доступных публикаций алгоритмов кеширования привело меня к мысли о том, что, возможно, LRU не самая эффективная схема. Для высоконагруженных приложений даже несколько процентов дополнительной эффективности само по себе хлеб. Перебрав все найденные мной, я остановился на алгоритме CART. Данная схема сочетает в себе несколько полезных достоинств:
Давайте взглянем насколько схема эффективна на практике. Так как у меня не было достаточно времени, чтобы имплементировать и отлаживать другие схемы, я буду сравнивать эффективность все с той же имплементацией LRU из библиотеки TBB. Сначала тестируем эффективность обеих схем на случайно сгенерированной последовательности чисел (0…10000) для трех размеров кеша (100, 500 и 1000): Less is better. CART незначительно опережает LRU по эффективности, но, честно говоря, совершенно случайный доступ не самый хороший тест. Более того - в реальном приложении с таким типом доступа эффективность любого кеша будет низкой. Поэтому второй тест я сделал больше похожим на практическое применение. Здесь числа берутся из 6-ти корзин, каждая из которых имеет все большее количество элементов, что уменьшает вероятность выбрать какое-то конкретное число. Таким образом числа от 0 до 150 суммарно имеют такую же вероятность быть выбранными, как и числа от 5000 до 10000. Эта тактика похожа на паттерн выборки, например, из базы данных пользователей, где часто заходящие пользователи чаще дергают базу. Bins draw, cache size 100 В этом случае CART уже показывает значительно лучшие результаты, нежели LRU. Чего, собственно, мы и хотели достичь. Всем интересующимся предлагаю скачать пример и запустить собственноручно. Найти эту имплементацию можно здесь. Она тестировалась в реальном приложении под нагрузкой, что впрочем не гарантирует 100% отсутствия возможных багов. Используйте, так сказать, на свой страх и риск. Для интеграции в свои проекты вам необходимы только файлы в папке Include. От зависимости на HashMurmur3 можно избавиться, заменив его на любую подходящую вам хеш-функцию. В зависимостях у этой имплементации есть только TBB, но большинство тех, кто пишет многопоточные приложения на С++, и так его используют. В качестве бонуса прилагается неплохая имплементация генератора случайных чисел. Также оригинальная имплементация использует EASTL вместо STL, но я избавился от этой зависимости перед тем, как выложить публичную версию. Исходники примеров собираются под VS2010, но порт под Linux не должен вызвать проблем. Так как у меня сейчас нет под рукой Linux"а, я был бы благодарен сообществу, если бы кто-то потратил на это немного своего времени и сделал этот порт. P.S. Способов применения хорошей схемы кеширования можно придумать массу. У меня, например, данная имплементация также используется в доступе к Memory Mapped Files, где файл маппится не целиком, что может привести к огромному расходу виртуальной памяти на больших файлах, а ограниченным количеством небольших кусков по 16Мб. Кеш при этом управляет тем, какие куски выталкивать из памяти. Также можно парой сотен строк написать себе свой memcached, если вдруг возникнет такое желание. Ссылки по теме
|
|