Dropping eBPF CPU Cost by About 90% with Memoization (Not AI Gen)
TL;DR Highlight
Caching the policy per inode cut kernel CPU by about 90% in an eBPF file-open security agent
Who Should Read
A systems developer building an eBPF or Linux security agent that slows down because it recomputes file access policy on every open
Core Mechanics
- The bottleneck was finding the policy, not enforcing it
- Profiling showed allow/deny enforcement was not the most expensive part.
- Figuring out which policy applies to a file open cost the most.
- Every open walked the full path
- Policies are path-based, so the LSM hook (a kernel hook that fires on file open) rebuilt the path and walked parent dentries (kernel directory-entry objects).
- Opening the same file or files in the same subtree repeated the same work each time.
- Inode-based cache key instead of dentries
- Dentries are pointers, which can't be stored in eBPF maps.
- The key has three fields: mount namespace ID, mount ID, and inode number.
- LRU hash map cache, value holds policy bit index and state
- The map type is BPF_MAP_TYPE_LRU_HASH with max_entries of 10000.
- The value holds access_index (bit position in the policy bitmask) and a state.
Community Reactions
- Memory cost wasn't measured
- One commenter noted memoization trades memory for CPU, and the post doesn't measure the memory side.
- The point was to measure rather than assume.
- Benchmark only opens the same file
- One commenter said the 90% figure is the best case of repeatedly opening one file.
- They expected a workload that never reopens files to be slightly slower from the cache writes.
- Cache invalidation and security concerns
- Several commenters asked how the cache is invalidated on directory moves, permission changes, new hardlinks, or deletions.
- One argued caching the applicable policy is a security risk if user actions can change it.
- Path-based rules and bind mounts
- One commenter asked how path-only rules handle bind mounts and other ways to make a file appear under a different path.
- The real story is cache key design
- One commenter said the interesting part is correctly caching path-to-policy mapping within eBPF and Linux filesystem limits.
How to Apply
- Profile your hot path first
- Today, spend 30 minutes collecting kernel cycles with perf record -e cycles:k on your hooked path.
- Check in the flamegraph whether enforcement or lookup is the expensive part.
- Put the full identity scope in the cache key
- Build the key from value-type fields such as mntns_id, mount_id, and inode since pointers can't go in maps.
- Don't use the inode number alone because it can collide across mount trees.
- Fall back to the slow path where the cache could be wrong
- Use the cache only when i_nlink is 1, so hardlinks take the slow path.
- Record skips in a stats counter to see how much cache coverage you lose.
- Benchmark cache misses too
- Run both repeated opens of one file and opens of all-different files and compare.
- Record the cache map's memory usage alongside.
Try It
# Paste into Claude or ChatGPT (simplified version of the post's method: a code review prompt)
I'm building an eBPF security agent that checks path-based policy on every file open through an LSM hook.
Currently it walks up dentries each time to find the policy.
Review the code below and check the risks of introducing an inode-based cache.
{{current policy lookup code}}
Cache key candidates: mount namespace ID, mount ID, inode number
Cache map: BPF_MAP_TYPE_LRU_HASH, max_entries 10000
Hardlinks (i_nlink != 1) skip the cache and use the slow path
Please answer:
1. List the cases where the cache could return a wrong result
2. Suggest a bypass condition for each case
3. Give a benchmark plan that measures both cache hits and misses
Next step: ask for a design on how to flush the cache on directory moves, permission changes, and policy updates.Terminology
Related Papers
Mercury 3: Diffusion at scale
Scaling diffusion LLM 40x gives Mercury 3: 4–8x faster than speed-tier models, similar quality
Timestral: Mistral's time-series foundation models
A 209M time-series model ranks first on three benchmarks and runs on CPU.
DeepSeek V4.1 Flash 1017 (Max)
DeepSeek V4.1 Flash 1017 Max scores 45 on the Intelligence Index at 211 tok/s and $0.30 input.
If you don't run your own LLM proxy, how do you know whats going on?
Running your own LLM proxy shows which keys, models, and cache misses drive your usage.
DeepSeek: DeepSeek v4.1 Flash 1017
DeepSeek V4.1 Flash 1017: $0.05/M input, 1M context, built for agents
DeepSeek-v4.1-Flash-1017
DeepSeek V4.1 Flash minor update lifts coding and agent benchmarks while cutting run time and output length
Show HN: Llmspeed – feel how fast a local LLM runs before you buy the hardware