Skip to content

Commit

Permalink
add cache_info (#43)
Browse files Browse the repository at this point in the history
* add `cache_info`

* typos
  • Loading branch information
ericphanson authored Dec 9, 2023
1 parent 0d2635d commit 3125353
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "LRUCache"
uuid = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637"
version = "1.5.1"
version = "1.6.0"

[compat]
julia = "1"
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ storing this value in `lru`. Also comes in the following form:

#### get(default::Callable, lru::LRU, key)

#### cache_info(lru::LRU)

Returns an object holding a snapshot of information about hits, misses, current size, and total size in its properties.

The caching functions `get` and `get!` each contribute a cache hit or miss on every function call (depending on whether or not the key was found). `empty!` resets the counts of hits and misses to 0.

The other functions, e.g. `getindex` `iterate`, `haskey`, `setindex!`, `delete!`, and `pop!` do not contribute cache hits or misses, regardless of whether or not a key is retrieved. This is because it is not possible to use these functions for caching.

## Example

Commonly, you may have some long running function that sometimes gets called with the same
Expand All @@ -130,3 +138,5 @@ function cached_foo(a::Float64, b::Float64)
end
end
```

If we want to see how our cache is performing, we can call `cache_info` to see the number of cache hits and misses.
61 changes: 59 additions & 2 deletions src/LRUCache.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module LRUCache

include("cyclicorderedset.jl")
export LRU
export LRU, cache_info

using Base.Threads
using Base: Callable
Expand All @@ -14,14 +14,60 @@ mutable struct LRU{K,V} <: AbstractDict{K,V}
keyset::CyclicOrderedSet{K}
currentsize::Int
maxsize::Int
hits::Int
misses::Int
lock::SpinLock
by::Any
finalizer::Any

function LRU{K, V}(; maxsize::Int, by = _constone, finalizer = nothing) where {K, V}
dict = Dict{K, V}()
keyset = CyclicOrderedSet{K}()
new{K, V}(dict, keyset , 0, maxsize, SpinLock(), by, finalizer)
new{K, V}(dict, keyset , 0, maxsize, 0, 0, SpinLock(), by, finalizer)
end
end

Base.@kwdef struct CacheInfo
hits::Int
misses::Int
currentsize::Int
maxsize::Int
end

function Base.show(io::IO, c::CacheInfo)
return print(io, "CacheInfo(; hits=$(c.hits), misses=$(c.misses), currentsize=$(c.currentsize), maxsize=$(c.maxsize))")
end

"""
cache_info(lru::LRU) -> CacheInfo
Returns a `CacheInfo` object holding a snapshot of information about the cache hits, misses, current size, and maximum size, current as of when the function was called. To access the values programmatically, use property access, e.g. `info.hits`.
Note that only `get!` and `get` contribute to hits and misses, and `empty!` resets the counts of hits and misses to 0.
## Example
```jldoctest
lru = LRU{Int, Float64}(maxsize=10)
get!(lru, 1, 1.0) # miss
get!(lru, 1, 1.0) # hit
get(lru, 2, 2) # miss
get(lru, 2, 2) # miss
info = cache_info(lru)
# output
CacheInfo(; hits=1, misses=3, currentsize=1, maxsize=10)
```
"""
function cache_info(lru::LRU)
lock(lru.lock) do
return CacheInfo(; hits=lru.hits, misses=lru.misses, currentsize=lru.currentsize, maxsize=lru.maxsize)
end
end

Expand Down Expand Up @@ -71,8 +117,10 @@ function Base.get(lru::LRU, key, default)
lock(lru.lock) do
if _unsafe_haskey(lru, key)
v = _unsafe_getindex(lru, key)
lru.hits += 1
return v
else
lru.misses += 1
return default
end
end
Expand All @@ -81,8 +129,10 @@ function Base.get(default::Callable, lru::LRU, key)
lock(lru.lock)
try
if _unsafe_haskey(lru, key)
lru.hits += 1
return _unsafe_getindex(lru, key)
end
lru.misses += 1
finally
unlock(lru.lock)
end
Expand All @@ -92,12 +142,14 @@ function Base.get!(lru::LRU{K, V}, key, default) where {K, V}
evictions = Tuple{K, V}[]
v = lock(lru.lock) do
if _unsafe_haskey(lru, key)
lru.hits += 1
v = _unsafe_getindex(lru, key)
return v
end
v = default
_unsafe_addindex!(lru, v, key)
_unsafe_resize!(lru, evictions)
lru.misses += 1
return v
end
_finalize_evictions!(lru.finalizer, evictions)
Expand All @@ -108,6 +160,7 @@ function Base.get!(default::Callable, lru::LRU{K, V}, key) where {K, V}
lock(lru.lock)
try
if _unsafe_haskey(lru, key)
lru.hits += 1
return _unsafe_getindex(lru, key)
end
finally
Expand All @@ -117,11 +170,13 @@ function Base.get!(default::Callable, lru::LRU{K, V}, key) where {K, V}
lock(lru.lock)
try
if _unsafe_haskey(lru, key)
lru.hits += 1
# should we test that this yields the same result as default()
v = _unsafe_getindex(lru, key)
else
_unsafe_addindex!(lru, v, key)
_unsafe_resize!(lru, evictions)
lru.misses += 1
end
finally
unlock(lru.lock)
Expand Down Expand Up @@ -247,6 +302,8 @@ function Base.empty!(lru::LRU{K, V}) where {K, V}
_unsafe_resize!(lru, evictions, 0)
lru.maxsize = maxsize # restore `maxsize`
end
lru.hits = 0
lru.misses = 0
end
_finalize_evictions!(lru.finalizer, evictions)
return lru
Expand Down
43 changes: 43 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LRUCache
using LRUCache: CacheInfo
using Test
using Random
using Base.Threads
Expand Down Expand Up @@ -253,4 +254,46 @@ end
@test lru[1] == 1:9
end

@testset "cache_info" begin
lru = LRU{Int, Float64}(; maxsize=10)

get!(lru, 1, 1.0) # miss
@test cache_info(lru) == CacheInfo(; hits=0, misses=1, currentsize=1, maxsize=10)

get!(lru, 1, 1.0) # hit
@test cache_info(lru) == CacheInfo(; hits=1, misses=1, currentsize=1, maxsize=10)

get(lru, 1, 1.0) # hit
@test cache_info(lru) == CacheInfo(; hits=2, misses=1, currentsize=1, maxsize=10)

get(lru, 2, 1.0) # miss
info = CacheInfo(; hits=2, misses=2, currentsize=1, maxsize=10)
@test cache_info(lru) == info
@test sprint(show, info) == "CacheInfo(; hits=2, misses=2, currentsize=1, maxsize=10)"

# These don't change the hits and misses
@test haskey(lru, 1)
@test cache_info(lru) == info
@test lru[1] == 1.0
@test cache_info(lru) == info
@test !haskey(lru, 2)
@test cache_info(lru) == info

# This affects `currentsize` but not hits or misses
delete!(lru, 1)
@test cache_info(lru) == CacheInfo(; hits=2, misses=2, currentsize=0, maxsize=10)

# Likewise setindex! does not affect hits or misses, just `currentsize`
lru[1] = 1.0
@test cache_info(lru) == info

# Only affects size
pop!(lru, 1)
@test cache_info(lru) == CacheInfo(; hits=2, misses=2, currentsize=0, maxsize=10)

# Resets counts
empty!(lru)
@test cache_info(lru) == CacheInfo(; hits=0, misses=0, currentsize=0, maxsize=10)
end

include("originaltests.jl")

4 comments on commit 3125353

@ericphanson
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error while trying to register: Register Failed
@ericphanson, it looks like you are not a publicly listed member/owner in the parent organization (JuliaCollections).
If you are a member/owner, you will need to change your membership to public. See GitHub Help

@ericphanson
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

New feature: cache_info(lru) provides some statistics about the cache.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/96816

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.6.0 -m "<description of version>" 31253530eacedd9f4522764f4002a7430d3080a4
git push origin v1.6.0

Please sign in to comment.