“函数缓存”或“记忆化”的主要作用是避免重复计算相同的输入,从而提高程序的性能。
函数缓存的应用场景非常广泛,尤其是在那些计算成本较高或需要频繁计算相同输入的场景中。
递归函数优化:在递归计算中,许多子问题可能会被重复计算多次。通过记忆化,可以存储已经计算过的子问题的结果,并在需要时直接返回,从而避免重复计算。例如,斐波那契数列的计算、动态规划中的子问题求解等。
复杂计算优化:对于需要大量计算资源的函数,记忆化可以显著提高性能。通过缓存计算结果,可以避免每次调用函数时都进行复杂的计算。这在科学计算、数值分析、机器学习等领域中特别有用。
auto f { [](auto&& f, int n){ if(n <= 2) return 1; return f(f, n-1) + f(f, n - 2); }};
数据库查询优化:在频繁访问数据库的场景中,可以使用记忆化来缓存查询结果。这样,当相同的查询再次发生时,可以直接从缓存中获取结果,而无需再次访问数据库。这可以大大减少数据库的访问次数,提高查询效率。
Web服务接口调用:在Web开发中,经常需要调用远程服务接口获取数据。对于相同的请求参数,如果远程服务的响应是固定的,那么可以使用记忆化来缓存响应结果。这样,当相同的请求再次发生时,可以直接返回缓存中的结果,避免了不必要的网络延迟和开销。
图形渲染和游戏开发:在这些领域中,许多计算密集型任务(如物理模拟、碰撞检测等)可能需要频繁执行。通过使用记忆化技术,可以将之前计算过的结果缓存起来,以便在需要时快速重用,从而提高渲染速度和游戏性能。
机器学习模型推理:在机器学习领域,模型推理通常涉及大量的计算。对于相同的输入数据,模型推理的结果应该是相同的。因此,可以使用记忆化来缓存推理结果,从而加速模型的响应速度。
缓存搜索结果:在搜索引擎或推荐系统中,用户经常会进行类似的搜索或请求相似的推荐。通过使用记忆化技术,可以缓存之前的搜索结果或推荐列表,以便在用户再次进行类似操作时快速返回结果。
/*
* memorization casher | delay calculation
*/
template <class F> class cacher : public function_traits<F> {
static_assert(std::same_as<std::decay_t<F>, F>, "const volatile and reference specifiers of function are not accepted");
public:
using typename function_traits<F>::callable_wrapper_type; //accordant std::function type
using typename function_traits<F>::return_type; //original return type for invoke result
using typename function_traits<F>::fundamental_return_type; //decayed bare return type for hashmap value
using typename function_traits<F>::fundamental_tuple_type; //decayed bare args pack for hashmap key
using typename function_traits<F>::tuple_type; //original args pack with trailing specifiers for invoke parems
private:
static_assert(requires{ typename std::hash<fundamental_tuple_type>; }, "std::hash could be specified with std::tuple<Args ...>");
//f: the invocable
std::optional<callable_wrapper_type> f;
//hash: the result memorizer
std::unordered_map<fundamental_tuple_type, std::decay_t<return_type>> hash;
public:
constexpr cacher(void) noexcept = default;
cacher(cacher &&) noexcept = default;
cacher& operator=(cacher &&) noexcept =default;
//copy construct/assignment is not allowed
cacher(cacher const &) = delete;
cacher& operator=(cacher const &) noexcept = delete;
//delay to assign functor
template <class U>
void assign(U&& __f) {
f = std::forward<U>(__f);
}
template <class U>
cacher(U&& __f) : f(std::forward<U>(__f)) {}
template <class... Args>
//override operator() to support the invocation
return_type operator()(Args&& ...args)
requires std::same_as<std::tuple<Args ...>, tuple_type>
{
//operator<T>::operator bool() implicit conversion
if(static_cast<bool>(f)) throw std::invalid_argument("the functor has not been assigned");
auto buffer { std::forward_as_tuple(std::decay_t<Args>(args) ...) };
if(hash.count(buffer)) return hash[buffer];
//std::optional acts like a pointer object
return (hash[buffer] = (*f)(std::forward<Args>(args) ...));
}
};
template <class U>
cacher(U&& f) -> cacher<std::decay_t<U>>;
其中function_traits
用于获取函数类型信息。通过模板元编程技术,它可以在编译时提取有关函数的参数类型、返回类型以及其他属性的信息。
function_traits
的主要目的是提供一种通用的方式来处理不同类型的函数对象,如普通函数、函数指针、lambda 表达式、成员函数指针以及自定义的函数对象等。通过
function_traits
,我们可以以一种统一的方式处理这些不同类型的函数对象,而无需针对每种类型编写特定的代码。实现
function_traits
的关键技术通常包括模板特化和可变参数模板。首先,定义一个基本的function_traits
模板类,然后通过特化这个模板类,将返回类型和参数类型作为模板参数,从而能够获取函数的类型、返回值和参数的个数。使用
function_traits
可以简化许多与函数类型相关的元编程任务,例如泛型编程中的类型推导、函数签名的匹配等。
/*
* type F meets std::invocable concept requires
* F(SomeArgs ...) -> SomeRet
*/
template <typename F> struct function_traits;
/*
* primitive function type that meets std::is_function requires
* Ret(Args ...) [const] [volatile] [&/&&] [noexcept]
* only Ret(Args ...) bare type with no trailing specs can be accepted
*/
template <typename Ret, typename... Args>
struct function_traits<Ret(Args ...)> {
static constexpr std::size_t Arity { sizeof...(Args) };
using fundamental_return_type = std::decay_t<Ret>;
using return_type = Ret;
using function_type = Ret(Args ...);
using callable_wrapper_type = std::function<Ret(Args ...)>;
typedef Ret(*function_pointer_type)(Args ...);
template <std::size_t I> struct args{
static_assert(I < Arity, "index out of range");
using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
using fundamental_type = std::decay_t<type>;
};
using fundamental_tuple_type = std::tuple<std::decay_t<Args> ...>;
using tuple_type = std::tuple<std::remove_cv_t<Args> ...>;
};
/*
* pointers to functions
*/
template <typename Ret, typename ...Args>
struct function_traits<Ret(*)(Args ...)> : function_traits<Ret(Args ...)> {};
/*
* std::function with primitive function wrapped within bebind
*/
template <typename Ret, typename ...Args>
struct function_traits<std::function<Ret(Args ...)>> : function_traits<Ret(Args ...)> {};
/*
* member function ptr
* convertible from std::mem_ptr
*/
#define FUNCTION_TRAITS(...)\
template <typename Ret, typename Class, typename... Args>\
struct function_traits<Ret(Class::*)(Args ...) __VA_ARGS__> : function_traits<Ret(Args ...)> {};
FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)
/*
* classes with overloaded operator()
* with lambda included
*/
template <typename Functor>
struct function_traits : function_traits<decltype(std::addressof(Functor::operator()))> {};
/*
* from any function object to std::function
* use function_traits purification
*/
template <typename F>
auto to_function(F const & lambda)
{ return static_cast<typename function_traits<F>::callable_wrapper_type>(lambda); }
template <typename F>
auto to_function(F && lambda)
{ return static_cast<typename function_traits<F>::callable_wrapper_type>(std::forward<F>(lambda)); }