缓存函数结果前需评估必要性:若函数执行快、参数组合少或调用频次低,缓存反而因哈希、内存、跳转开销而拖慢程序;嵌入式或高频循环中cache hit rate低于60%应警惕。

缓存函数结果前,先确认是否真需要
直接上 std::map 或 std::unordered_map 做备忘录,很容易;但很多场景下反而拖慢程序。比如函数本身执行很快(如几个整数运算)、输入参数组合极少、或调用频次极低——这时缓存带来的哈希计算、内存分配、查找跳转开销,可能比重复计算还高。尤其在嵌入式或高频循环中,cache hit rate 低于 60% 就该警惕。
实操建议:
- 先用
std::chrono粗略测下原函数耗时,若平均 - 记录实际调用参数分布(可临时加
std::cout ),确认是否存在重复模式 - 避免对带副作用的函数缓存(如含
rand()、time(nullptr)、IO 的函数),否则结果会“假一致”
用 std::unordered_map 实现线程不安全但高效的缓存
这是最常用、也最容易写出 bug 的方式。核心是把参数序列化为唯一 key,映射到返回值。C++ 没有内置元组哈希(C++17 起 std::hash<:tuple>></:tuple> 才可用),所以常见做法是手动拼接或用 std::hash 组合:
template <typename R, typename... Args>
class memoize {
std::unordered_map<std::size_t, R> cache_;
std::function<R(Args...)> func_;
<p>public:
memoize(std::function<R(Args...)> f) : func_(f) {}</p><pre class="brush:php;toolbar:false;">R operator()(Args... args) {
// 简单哈希:用 std::hash 对每个参数 hash 后异或(注意:异或不保序,但对缓存够用)
auto key = hash_args(args...);
auto it = cache_.find(key);
if (it != cache_.end()) return it->second;
R result = func_(args...);
cache_[key] = result;
return result;
}private:
template
std::size_t hash_args() { return 0; }
template <typename T, typename... Rest>
std::size_t hash_args(const T& t, const Rest&... rest) {
return hash_arg(t) ^ (hash_args(rest...) << 1);
}};
立即学习“C++免费学习笔记(深入)”;
注意点:
-
std::unordered_map::operator[]会在 key 不存在时默认构造 value,对非默认可构造类型(如std::unique_ptr)会编译失败,此时改用insert()或try_emplace() - 上面的哈希组合只是示意,
^和左移容易碰撞,生产环境建议用boost::hash_combine或自定义哈希器 - 没加锁,多线程并发调用同一
memoize实例会崩溃——别指望“读多写少”就省锁,unordered_map::find+insert不是原子操作
线程安全版:读写锁 or std::shared_mutex(C++17)
如果必须多线程共享一个缓存实例,std::shared_mutex 是比 std::mutex 更合适的选择:多个读可以并行,仅写入(即首次计算+插入)需独占。
关键修改点:
- 成员变量加
mutable std::shared_mutex mtx_;(mutable允许 const 成员函数里加锁) -
operator()改为 const,并在查找时用std::shared_lock,插入时用std::unique_lock - 避免“先 unlock 再 insert”导致的重复计算竞争:必须在持有
unique_lock期间完成查找+插入(即用try_emplace或insert的返回值判断)
示例片段:
R operator()(Args... args) const {
auto key = hash_args(args...);
std::shared_lock<std::shared_mutex> lock(mtx_);
auto it = cache_.find(key);
if (it != cache_.end()) return it->second;
<pre class="brush:php;toolbar:false;">// 释放读锁,获取写锁
lock.unlock();
std::unique_lock<std::shared_mutex> wlock(mtx_);
// 再查一次:防止其他线程已插入
it = cache_.find(key);
if (it != cache_.end()) return it->second;
R result = func_(args...);
cache_.emplace(key, result); // emplace 不会覆盖已有 key
return result;}
更轻量的替代:局部 static map + 函数内联
如果函数是纯计算、参数类型固定、且只在单个函数内调用,比泛型 memoize 类更简单可靠:
int expensive_calc(int a, int b) {
static std::unordered_map<std::pair<int, int>, int> cache;
static std::mutex mtx;
<pre class="brush:php;toolbar:false;">auto key = std::make_pair(a, b);
std::lock_guard<std::mutex> lock(mtx);
auto it = cache.find(key);
if (it != cache.end()) return it->second;
int result = /* real computation */;
cache[key] = result;
return result;}
优点是无模板膨胀、生命周期明确;缺点是无法复用、静态变量初始化存在 static initialization order fiasco 风险(不过 unordered_map 是平凡类型,C++11 起保证零初始化)。真正要注意的是:别在 constexpr 上下文或 DLL 导出函数里用它——静态局部变量的地址在不同模块可能不一致。
缓存失效永远比缓存本身更难处理。没设计清除逻辑的备忘录,三个月后大概率变成内存泄漏源。


















