Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ jobs:
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- cached-docs-preview -->';
const marker = '<!-- memoizationkit-docs-preview -->';
const issue_number = context.issue.number;
const url = `https://lkdvos.github.io/Cached.jl/previews/PR${issue_number}/`;
const url = `https://lkdvos.github.io/MemoizationKit.jl/previews/PR${issue_number}/`;
const body = `${marker}\n[Preview the documentation](${url})`;
const comments = await github.paginate(github.rest.issues.listComments, {
...context.repo,
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Layout

This repository uses sibling worktrees with the bare Git repository at `../Cached.jl`.
This repository uses sibling worktrees with a shared bare Git repository in the parent directory.
Create additional worktrees beside the current checkout, never inside it.

- `src/`: memoization macro, strategy dispatch, cache containers, registry, and configuration.
Expand Down Expand Up @@ -37,11 +37,11 @@ Keep Markdown prose to one sentence per line.

## Implementation constraints

- `@cached` preserves the method signature and moves the body to `Cached.implementation`.
- `@cached` preserves the method signature and moves the body to `MemoizationKit.implementation`.
- The default global cache shares one size limit across a function's cached methods, with a separate cache per container type.
- Preserve inferred return types and allocation-free RAM hits for concrete keys with the built-in containers.
- Built-in RAM containers distinguish key types and use `hash` and `isequal`.
- `Cached.cachekey` may merge equivalent inputs; merged calls must accept the same result and return type.
- `MemoizationKit.cachekey` may merge equivalent inputs; merged calls must accept the same result and return type.
- Compute misses outside cache locks so recursion works; concurrent misses may compute twice.
- Keep the call path safe for precompiled packages, without runtime method definitions.
- Disk lookup order is RAM, read-only artifact, local database, then computation.
Expand Down
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name = "Cached"
name = "MemoizationKit"
uuid = "1b238080-9255-4fe9-b224-89eb24efe93b"
version = "0.1.0"
authors = ["Lukas Devos <ldevos98@gmail.com> and contributors"]
Expand All @@ -20,9 +20,9 @@ Tachikoma = "468859d6-42d8-48b7-8ad9-1d312e0e3b0a"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"

[extensions]
CachedSQLiteExt = "SQLite"
CachedTachikomaExt = "Tachikoma"
CachedTimerOutputsExt = "TimerOutputs"
MemoizationKitSQLiteExt = "SQLite"
MemoizationKitTachikomaExt = "Tachikoma"
MemoizationKitTimerOutputsExt = "TimerOutputs"

[compat]
ExprTools = "0.1"
Expand Down
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Cached.jl
# MemoizationKit.jl

[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://lkdvos.github.io/Cached.jl/stable/)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://lkdvos.github.io/Cached.jl/dev/)
[![Build Status](https://github.com/lkdvos/Cached.jl/actions/workflows/Tests.yml/badge.svg?branch=main)](https://github.com/lkdvos/Cached.jl/actions/workflows/Tests.yml?query=branch%3Amain)
[![Coverage](https://codecov.io/gh/lkdvos/Cached.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/lkdvos/Cached.jl)
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://lkdvos.github.io/MemoizationKit.jl/stable/)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://lkdvos.github.io/MemoizationKit.jl/dev/)
[![Build Status](https://github.com/lkdvos/MemoizationKit.jl/actions/workflows/Tests.yml/badge.svg?branch=main)](https://github.com/lkdvos/MemoizationKit.jl/actions/workflows/Tests.yml?query=branch%3Amain)
[![Coverage](https://codecov.io/gh/lkdvos/MemoizationKit.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/lkdvos/MemoizationKit.jl)
[![Code Style: Runic](https://img.shields.io/badge/code_style-%F0%9F%AA%A8_Runic-9558B2)](https://github.com/fredrikekre/Runic.jl)
[![Aqua](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)

Expand All @@ -13,12 +13,12 @@ Memoization for Julia, with bounded memory, persistent results, and a live view
- **Live dashboard:** watch hit rates and sizes, browse RAM and disk caches, and clear or resize RAM caches from your terminal.
- **Fast RAM hits:** preserve return-type inference and avoid allocations with the built-in caches and concrete keys.
- **Flexible strategies:** share results across tasks, keep them local to a task, or skip caching for selected argument types.
- **Custom keys:** reuse results for equivalent inputs with `Cached.cachekey`, or customize hashing and equality with `Hashed`.
- **Custom keys:** reuse results for equivalent inputs with `MemoizationKit.cachekey`, or customize hashing and equality with `Hashed`.
- **Bounded storage:** Clock and LRU eviction, with limits in entries or bytes across a function's methods.
- **Package controls:** set defaults per package or function, inspect caches programmatically, and profile lookups and computations with TimerOutputs.jl.

```julia
using Cached
using MemoizationKit

@cached function fib(n::Int)::BigInt
n < 2 ? BigInt(n) : fib(n - 1) + fib(n - 2)
Expand All @@ -36,13 +36,13 @@ Load SQLite.jl and opt a function into disk caching:

```julia
using SQLite
Cached.DiskCacheStyle(::typeof(fib), ::Int) = DiskCache()
MemoizationKit.DiskCacheStyle(::typeof(fib), ::Int) = DiskCache()
fib(200) # new results are kept in RAM and on disk
```

Calls look in RAM, then on disk, before computing.
Processes on the same machine share the database.
You control result versions and disk cleanup; see [disk caching](https://lkdvos.github.io/Cached.jl/dev/disk/).
You control result versions and disk cleanup; see [disk caching](https://lkdvos.github.io/MemoizationKit.jl/dev/disk/).

## See what your caches are doing

Expand All @@ -63,14 +63,14 @@ The dashboard shows recent hit rates, storage use, and activity, with separate R
⇥ tab ↑↓ select ⏎ resize e empty s sort / filter q quit
```

See the [dashboard guide](https://lkdvos.github.io/Cached.jl/dev/dashboard/) for controls and statistics.
See the [dashboard guide](https://lkdvos.github.io/MemoizationKit.jl/dev/dashboard/) for controls and statistics.

## Why Cached?
## Why MemoizationKit?

Choose Cached when memoization needs ongoing management: memory limits, reuse across runs, or visibility into a running workload.
Choose MemoizationKit when memoization needs ongoing management: memory limits, reuse across runs, or visibility into a running workload.
It brings these tools together with strategies chosen by function and argument type.
For a single in-memory cache, Memoize.jl or Memoization.jl with an LRU container may already cover your needs.
See [the comparison and tradeoffs](https://lkdvos.github.io/Cached.jl/dev/#why-cached).
See [the comparison and tradeoffs](https://lkdvos.github.io/MemoizationKit.jl/dev/#why-memoizationkit).

Requires Julia 1.10 or later.
Start with [usage](https://lkdvos.github.io/Cached.jl/dev/#usage), [configuration](https://lkdvos.github.io/Cached.jl/dev/configuration/), or [timing](https://lkdvos.github.io/Cached.jl/dev/timing/) in the [documentation](https://lkdvos.github.io/Cached.jl/dev/).
Start with [usage](https://lkdvos.github.io/MemoizationKit.jl/dev/#usage), [configuration](https://lkdvos.github.io/MemoizationKit.jl/dev/configuration/), or [timing](https://lkdvos.github.io/MemoizationKit.jl/dev/timing/) in the [documentation](https://lkdvos.github.io/MemoizationKit.jl/dev/).
4 changes: 2 additions & 2 deletions benchmark/Project.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Cached = "1b238080-9255-4fe9-b224-89eb24efe93b"
MemoizationKit = "1b238080-9255-4fe9-b224-89eb24efe93b"
LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[sources]
Cached = {path = ".."}
MemoizationKit = {path = ".."}
8 changes: 4 additions & 4 deletions benchmark/containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
# The value computation is trivial, so the numbers measure cache overhead only.

using BenchmarkTools, Random, Printf
using Cached: Cached
using MemoizationKit: MemoizationKit
using LRUCache: LRUCache

const CONTAINERS = [
"Cached.LRU" => (K, V, n) -> Cached.LRU{K, V}(; maxsize = n),
"Cached.ClockCache" => (K, V, n) -> Cached.ClockCache{K, V}(; maxsize = n),
"MemoizationKit.LRU" => (K, V, n) -> MemoizationKit.LRU{K, V}(; maxsize = n),
"MemoizationKit.ClockCache" => (K, V, n) -> MemoizationKit.ClockCache{K, V}(; maxsize = n),
"LRUCache.LRU" => (K, V, n) -> LRUCache.LRU{K, V}(; maxsize = n),
]

Expand All @@ -31,7 +31,7 @@ function run!(c, seq)
end

hitrate(c::LRUCache.LRU) = (i = LRUCache.cache_info(c); i.hits / (i.hits + i.misses))
hitrate(c) = (s = Cached.cache_stats(c); s.hits / (s.hits + s.misses))
hitrate(c) = (s = MemoizationKit.cache_stats(c); s.hits / (s.hits + s.misses))

function threaded!(c, seq, ntasks)
chunks = Iterators.partition(seq, cld(length(seq), ntasks))
Expand Down
10 changes: 5 additions & 5 deletions benchmark/disk_stress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#
# `test/test_disk.jl` includes this file and calls `stress` on a temporary directory.

using Cached
using MemoizationKit
using SQLite: SQLite

const COMPUTED = Ref(0)
Expand All @@ -21,8 +21,8 @@ const COMPUTED = Ref(0)
# preference, added to the load path.
function use_disk_path(dir)
env = mktempdir()
write(joinpath(env, "Project.toml"), "[deps]\nCached = \"$(Base.PkgId(Cached).uuid)\"\n")
write(joinpath(env, "LocalPreferences.toml"), "[Cached]\ndisk_path = $(repr(dir))\n")
write(joinpath(env, "Project.toml"), "[deps]\nMemoizationKit = \"$(Base.PkgId(MemoizationKit).uuid)\"\n")
write(joinpath(env, "LocalPreferences.toml"), "[MemoizationKit]\ndisk_path = $(repr(dir))\n")
push!(LOAD_PATH, env)
return env
end
Expand All @@ -31,8 +31,8 @@ end
stress_expected(k) = [sin(k * i) for i in 1:(64 + 37k % 512)]

@cached stress_value(k::Int)::Vector{Float64} = (COMPUTED[] += 1; stress_expected(k))
Cached.CacheStyle(::typeof(stress_value), ::Int) = NoCache()
Cached.DiskCacheStyle(::typeof(stress_value), ::Int) = DiskCache()
MemoizationKit.CacheStyle(::typeof(stress_value), ::Int) = NoCache()
MemoizationKit.DiskCacheStyle(::typeof(stress_value), ::Int) = DiskCache()

# Every key three times, in an order that depends on `seed`.
function stress_worker(dir, nkeys, seed)
Expand Down
4 changes: 2 additions & 2 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[deps]
Cached = "1b238080-9255-4fe9-b224-89eb24efe93b"
MemoizationKit = "1b238080-9255-4fe9-b224-89eb24efe93b"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9"
Tachikoma = "468859d6-42d8-48b7-8ad9-1d312e0e3b0a"
Expand All @@ -12,4 +12,4 @@ Tachikoma = "2.7"
TimerOutputs = "0.5, 1"

[sources]
Cached = {path = ".."}
MemoizationKit = {path = ".."}
6 changes: 3 additions & 3 deletions docs/examples/package.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ let env = mktempdir(), uid = Base.UUID(rand(UInt128))
mkpath(src)
write(
joinpath(dirname(src), "Project.toml"),
"name = \"MyPackage\"\nuuid = \"$uid\"\n[deps]\nCached = \"$(Base.PkgId(Cached).uuid)\"\n",
"name = \"MyPackage\"\nuuid = \"$uid\"\n[deps]\nMemoizationKit = \"$(Base.PkgId(MemoizationKit).uuid)\"\n",
)
write(
joinpath(src, "MyPackage.jl"),
"__precompile__(false)\nmodule MyPackage\nusing Cached\n@cached expensive(x) = x^2\nend\n",
"__precompile__(false)\nmodule MyPackage\nusing MemoizationKit\n@cached expensive(x) = x^2\nend\n",
)
pushfirst!(LOAD_PATH, env)
package = try
Expand All @@ -19,7 +19,7 @@ let env = mktempdir(), uid = Base.UUID(rand(UInt128))
end
write(
joinpath(env, "Project.toml"),
"[deps]\nCached = \"$(Base.PkgId(Cached).uuid)\"\nMyPackage = \"$uid\"\n",
"[deps]\nMemoizationKit = \"$(Base.PkgId(MemoizationKit).uuid)\"\nMyPackage = \"$uid\"\n",
)
with_preferences = function (f)
original = Base.ACTIVE_PROJECT[]
Expand Down
12 changes: 6 additions & 6 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using Cached: Cached
using MemoizationKit: MemoizationKit
using Documenter: Documenter, DocMeta, deploydocs, makedocs

DocMeta.setdocmeta!(Cached, :DocTestSetup, :(using Cached); recursive = true)
DocMeta.setdocmeta!(MemoizationKit, :DocTestSetup, :(using MemoizationKit); recursive = true)

makedocs(;
modules = [Cached],
modules = [MemoizationKit],
authors = "Lukas Devos",
sitename = "Cached.jl",
sitename = "MemoizationKit.jl",
format = Documenter.HTML(;
canonical = "https://lkdvos.github.io/Cached.jl",
canonical = "https://lkdvos.github.io/MemoizationKit.jl",
edit_link = "main",
assets = String[],
),
Expand All @@ -31,4 +31,4 @@ makedocs(;
],
)

deploydocs(; repo = "github.com/lkdvos/Cached.jl", devbranch = "main", push_preview = true)
deploydocs(; repo = "github.com/lkdvos/MemoizationKit.jl", devbranch = "main", push_preview = true)
38 changes: 19 additions & 19 deletions docs/src/configuration.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Configuration

```@meta
CurrentModule = Cached
CurrentModule = MemoizationKit
```

Default cache settings are read with [Preferences.jl](https://github.com/JuliaPackaging/Preferences.jl) from `LocalPreferences.toml`, next to the active project.
Expand All @@ -11,8 +11,8 @@ Default cache settings are read with [Preferences.jl](https://github.com/JuliaPa
| Key | Values | Default | Meaning |
| :--- | :--- | :--- | :--- |
| `maxsize` | integer ≥ 0 | `10000` | limit of a function's cache, all signatures together, in entries or bytes (see `measure`) |
| `measure` | `"count"` or `"bytes"` | `"count"` | count entries, or measure values with [`Cached.cachesize`](@ref) |
| `container` | `"ClockCache"` or `"LRU"` | `"ClockCache"` | container of the default `CacheStyle`; only in the `[Cached]` section |
| `measure` | `"count"` or `"bytes"` | `"count"` | count entries, or measure values with [`MemoizationKit.cachesize`](@ref) |
| `container` | `"ClockCache"` or `"LRU"` | `"ClockCache"` | container of the default `CacheStyle`; only in the `[MemoizationKit]` section |
| `disk` | `true` or `false` | `true` | whether the [disk cache](disk.md) of a function with a `DiskCacheStyle` is used |
| `disk_path` | a directory | `""` (a scratch space) | where the [disk caches](disk.md) are stored |

Expand All @@ -21,24 +21,24 @@ Default cache settings are read with [Preferences.jl](https://github.com/JuliaPa
Settings are looked up per function, in this order (first match wins):

1. runtime calls: [`set_cache_size!`](@ref);
2. the function's own section, `[<Package>.Cached.<function>]`;
3. the section of the package that owns the function, `[<Package>.Cached]`;
4. Cached's own section, `[Cached]`;
2. the function's own section, `[<Package>.MemoizationKit.<function>]`;
3. the section of the package that owns the function, `[<Package>.MemoizationKit]`;
4. MemoizationKit's own section, `[MemoizationKit]`;
5. the built-in defaults above.

The package that owns a function is the package of the module that defines it, `parentmodule(typeof(f))`.
For a function extended by several packages, that is the package that defines the function, not the ones that add cached methods to it.

```toml
# LocalPreferences.toml
[Cached]
[MemoizationKit]
maxsize = 10000
container = "LRU"

[MyPackage.Cached]
[MyPackage.MemoizationKit]
maxsize = 50000

[MyPackage.Cached.expensive]
[MyPackage.MemoizationKit.expensive]
measure = "bytes"
maxsize = 2_000_000_000
```
Expand All @@ -50,18 +50,18 @@ Unknown keys and invalid values are ignored with a warning.
- `disk` and `disk_path` are read when a function first uses its disk cache in the session.
- `maxsize` and `measure` are read when a function's first cache is created, so a change applies to functions that have not been called yet in the current session, and to every function after a restart.
- `container` is a compile-time preference, because it selects the default `CacheStyle`.
Changing it recompiles Cached on the next start.
Changing it recompiles MemoizationKit on the next start.
`GlobalCache()` and `TaskLocalCache()` use this container, so `CacheStyle` methods that return them follow the preference.

Use [`set_cache_preferences!`](@ref) to write the sections, which merges with what is already there:

```@example preferences
using Cached
sample = include(joinpath(pkgdir(Cached), "docs", "examples", "package.jl")) # hide
using MemoizationKit
sample = include(joinpath(pkgdir(MemoizationKit), "docs", "examples", "package.jl")) # hide
MyPackage = sample.package # hide
with_preferences = sample.with_preferences # hide
with_preferences() do # hide
set_cache_preferences!(; maxsize = 50_000) # [Cached]
set_cache_preferences!(; maxsize = 50_000) # [MemoizationKit]
println(read("LocalPreferences.toml", String))
end # hide
```
Expand All @@ -70,8 +70,8 @@ For functions defined in your package, use package or function settings:

```@example preferences
with_preferences() do # hide
set_cache_preferences!(MyPackage; measure = "bytes") # [MyPackage.Cached]
set_cache_preferences!(MyPackage.expensive; maxsize = 1000) # [MyPackage.Cached.expensive]
set_cache_preferences!(MyPackage; measure = "bytes") # [MyPackage.MemoizationKit]
set_cache_preferences!(MyPackage.expensive; maxsize = 1000) # [MyPackage.MemoizationKit.expensive]
println(read("LocalPreferences.toml", String))
end # hide
```
Expand All @@ -87,20 +87,20 @@ end # hide

## Measuring sizes

With `measure = "bytes"`, every cached value is measured once, on insertion, by [`Cached.cachesize`](@ref), which defaults to `Base.summarysize`.
With `measure = "bytes"`, every cached value is measured once, on insertion, by [`MemoizationKit.cachesize`](@ref), which defaults to `Base.summarysize`.
That traverses the whole value, which can be slow for large nested values, and counts memory shared between values once per value.
Keys and container overhead are excluded; this is a value budget, not a limit on total process memory.
Overload it for your own types:

```jldoctest
using Cached
using MemoizationKit

struct MyTensor
data::Vector{Float64}
end
Cached.cachesize(t::MyTensor) = sizeof(t.data);
MemoizationKit.cachesize(t::MyTensor) = sizeof(t.data);

Cached.cachesize(MyTensor(zeros(10)))
MemoizationKit.cachesize(MyTensor(zeros(10)))

# output

Expand Down
Loading
Loading