
在大型代码库中,我们经常会遇到一些令人不快的设计,这些设计会损害我们的性能。我们可能正在寻找一种非侵入式的方法来提升性能。例如,您可能不想更改函数签名。
让我们考虑一个具体的例子。也许有人设计了编程接口,让你必须使用索引来访问 map 中的值。他们可能有这样的代码:
“`cpp
自动 at_index(map_like auto&index_map,size_t idx){
size_t 计数 = 0;
对于(const auto&[键,值]:索引图){
如果(count == idx)
返回值;
计数++;
}
抛出 std::out_of_range(“索引超出范围”);
}
“`
这段代码遍历了 map 中 `idx` 的键。通常,这意味着某种链表遍历。如果你坚持使用这个接口,遍历值可能意味着重复调用 `at_index` 函数:
“`cpp
for (size_t i = 0; i < input_size; ++i) { at_index(index_map, i); } “` 如果您学过计算机科学,就会立即发现问题:我的代码具有二次复杂度。如果将地图大小加倍,则运行时间可能会翻两番。如果地图中有 2 或 4 个元素,则可能没问题,但如果有 400 个元素,则肯定不行。正确的解决方案是避免这样的设计。如果可以直接访问地图,则可以直接对其进行迭代:“`cpp for (auto& [key, value] : index_map) { sum += value; } “` 但是如果您遇到困难怎么办?那么您可以使用静态或 `thread_local` 缓存。关键在于将地图中的位置保存在缓存中,并从那里开始下一个查询。如果用户通常按顺序查询,那么您的缓存应该会极大地加快功能速度。 “`cpp auto at_index_thread_local_cache(map_like auto& index_map, size_t idx) { using iterator = decltype(index_map.begin()); struct Cache { iterator last_iterator; size_t last_index = -1; }; thread_local Cache cache; if (idx == cache.last_index + 1 && cache.last_iterator != index_map.end()) { cache.last_iterator++; cache.last_index = idx; if (cache.last_iterator != index_map.end()) { return cache.last_iterator->second;
} 别的 {
抛出 std::out_of_range(“索引超出范围”);
}
} 别的 {
缓存.last_iterator = index_map.begin();
缓存.last_index = -1;
size_t 计数 = 0;
对于(自动它=index_map.begin();
它!= index_map.end();++它){
如果(count == idx){
缓存.last_iterator = 它;
缓存.last_index = idx;
返回它->第二;
}
计数++;
}
抛出 std::out_of_range(“索引超出范围”);
}
}
“`
在 C++ 中,“thread_local”变量是指在同一线程中只有一个该变量的实例(由所有函数调用共享)。如果您希望整个程序只有一个该变量的实例,可以使用“static”,但在本例中,“thread_local”是最佳选择。您可能担心“thread_local”变量的性能影响,但它通常非常便宜:访问或修改它时,我们只需添加几条指令。
我们的缓存变量会记住每个线程上次访问的迭代器和索引。如果请求下一个索引,我们只需递增迭代器并返回。如果访问是非顺序的或第一次调用,它会从头开始回退到线性扫描,并在此过程中重建缓存。
代码更复杂,如果您不按顺序访问键,速度可能会更慢。然而,性能提升可能是巨大的。[我编写了一个基准测试程序,使用包含 400 个元素的 Map 进行测试](https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2025/10/19/)。
| 方法 | ns/key | 说明/key |
| ——– | ——– | ——– |
| 原版 | 300 | 2000 |
| 缓存 | 2 | 17 |
就我而言,缓存使性能提高了 150 倍。还不错。
原文: https://lemire.me/blog/2025/10/19/speeding-up-c-functions-with-a-thread_local-cache/