Comments 30
Ну и довольно медленный он, по крайней мере по сравнению с мьютексами
Сам бенчмарков не делал, столь категорично утверждать не буду, но как универсальное решение тоже бы не посоветовал.
Документация описывает 2 конкретных случаях, когда sync.Map выгоднее мапки с мьютексом, но в общем случае для реализации кеша может сыграть как в плюс так и в минус.
go c.GC()
return nil
}
error и return nil можно смело убрать.
еще 5 копеек
— не стоит светить все функции наружу, тот же GC, есть интерфейс Get/Set/Delete остальные функции не должны быть доступны снаружи
— defer не бесплатен, когда функции делает много под блокировкой, это оправдано, для чтения/записи в map это избыточно
— не надо держать блокировку без необходимости, получил блокировку, прочитал/записал в мапку, отпустил блокировку и дальше уже разбираешься с тем что получил (это про Get)
— зачем delete возврат ошибки? она бесполезна + ради этого двойной поиск в мапке
— сборка мусора вообще странно сделана, зачем в два этапа? нет гарантии что между expiredKeys и clearItems значения не будут обновлены
А зачем в структуре Item Duration? Мы при добавлении нового элемента вычисляем когда он протухнет и в дальнейшем время жизни никак не используем, зачем тогда храним?
Duration необходим для вычисления значения expiration, которое используется в методах Get и GC
Еще вопрос: если время жизни по-умолчанию будет 10 и при добавлении нового элемента я захочу, чтоб он не протухал и установлю duration 0, правильно ли я понимаю, что желаемого я не получу? (в go просто новичок)
Прошу прощение, не уточнил этот момент в статье: что бы кеш не протухал необходимо установить значение duration равное -1, в этом случае expiration будет равен 0.
А насчет duration: он нужен будучи параметром метода Set, но не параметром item, который нигде не используется
Duration необходим для вычисления значения expiration, которое используется в методах Get и GCХоть убейте, не могу найти строчку, где вы считываете это поле (не путайте его с аргументом функции
duration
).C типами данных: Strings/Lists of Strings/Dictionaries of Strings.
И таким же TTL. :)
github.com/genesem/kvdb/blob/master/docs_russian.md
Смотрели имеющиеся реализации встраиваемых кешей, например, groupcache?
Интересно бы узнать, почему не подошло, какие дополнительные фичи потребовались.
Кэш без контроля размера это просто еще один способ организовать утечку памяти.
Баловался этой темой. Если нужна скорость, то надо меньше локов и отказываться от interface. Поэтому приходится прощаться с map, sync.map.
В итоге решение было с генератором кэша под заданную структуру. https://github.com/JekaMas/nicecache
На чтение и запись в несколько горутин получалось выйти в 60-80нс.
В такой реализации будет катастрофа на большом количестве элементов. Под map будет выделяться и удерживаться избыточная память, даже когда все элементы протухнут. GC (тот, который родной гошный) будет каждый раз ходить по всем элементам. Чтобы избежать таких проблем используется sync.Map, и для него достаточно написать один time.Ticker.
sync.Map не решит проблему с gc, т.к. хранящиеся в мапке указатели никуда не денутся
здесь только один путь — уменьшать кол-во указателей, как, например, сделали вот эти ребята: allegro.tech/2016/03/writing-fast-cache-service-in-go.html
а вот и нет! смотрим 96 строку sync/map.go: entry{p: unsafe.Pointer(&i)
поэтому GC и не нагружается из-за хранения unsafe-ссылок на значения в sync.Map
Есть какие-нибудь тесты, которые подтверждают облегчение жизни gc при использовании sync.Map?
В коде вижу вот это:
return &entry{p: unsafe.Pointer(&i)}
создается ссылка на entry и далее она хранится в
map[interface{}]*entry
Сокращения кол-ва ссылок не вижу, плюс объект, ссылка на который сохранена в map, продолжает жить в куче. В чем профит.
Пишем простой менеджер кеша в памяти на Go