【函数缓存 | 记忆化技术】(类型萃取 | 延迟计算 | hash | 计算优化 | 函数式编程 | 模板元编程)

时间:2024-03-10 08:42:29

        “函数缓存”或“记忆化”的主要作用是避免重复计算相同的输入,从而提高程序的性能。

        函数缓存的应用场景非常广泛,尤其是在那些计算成本较高或需要频繁计算相同输入的场景中。

  1. 递归函数优化:在递归计算中,许多子问题可能会被重复计算多次。通过记忆化,可以存储已经计算过的子问题的结果,并在需要时直接返回,从而避免重复计算。例如,斐波那契数列的计算、动态规划中的子问题求解等。

  2. 复杂计算优化:对于需要大量计算资源的函数,记忆化可以显著提高性能。通过缓存计算结果,可以避免每次调用函数时都进行复杂的计算。这在科学计算、数值分析、机器学习等领域中特别有用。

    	auto f { [](auto&& f, int n){
    		if(n <= 2) return 1;
    		return f(f, n-1) + f(f, n - 2);
    	}};

  3. 数据库查询优化:在频繁访问数据库的场景中,可以使用记忆化来缓存查询结果。这样,当相同的查询再次发生时,可以直接从缓存中获取结果,而无需再次访问数据库。这可以大大减少数据库的访问次数,提高查询效率。

  4. Web服务接口调用:在Web开发中,经常需要调用远程服务接口获取数据。对于相同的请求参数,如果远程服务的响应是固定的,那么可以使用记忆化来缓存响应结果。这样,当相同的请求再次发生时,可以直接返回缓存中的结果,避免了不必要的网络延迟和开销。

  5. 图形渲染和游戏开发:在这些领域中,许多计算密集型任务(如物理模拟、碰撞检测等)可能需要频繁执行。通过使用记忆化技术,可以将之前计算过的结果缓存起来,以便在需要时快速重用,从而提高渲染速度和游戏性能。

  6. 机器学习模型推理:在机器学习领域,模型推理通常涉及大量的计算。对于相同的输入数据,模型推理的结果应该是相同的。因此,可以使用记忆化来缓存推理结果,从而加速模型的响应速度。

  7. 缓存搜索结果:在搜索引擎或推荐系统中,用户经常会进行类似的搜索或请求相似的推荐。通过使用记忆化技术,可以缓存之前的搜索结果或推荐列表,以便在用户再次进行类似操作时快速返回结果

 

/*
*   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)); }