C++进阶指南:优化程序性能之惰性求值

文摘   2024-11-12 12:06   北京  

你好,我是雨乐~

这几天看了篇文章,聊到提升程序运行性能的,感觉挺有意思,所以分享给大家。

先看一段代码:

int multi(int x, int y) { 
  return x * y;
}

int main() {
int x =2, y =3;
int tmp =multi(x, y);
int result =multi(4, tmp);
// 其他逻辑操作
std::cout <<"result: "<< result <<"\n";

return0;
}

这段代码很简单,首先调用multi(x,y)计算器结果并存储到变量tmp,然后接着调用multi函数并将结果存储大变量result,接着就是其他逻辑操作,知道函数结尾才用到result操作。

这段代码符合逻辑直观,相信很多人会这么写。在很多时候,我们进行的计算结果不一定马上使用,而是临到最后才用,亦或者压根用不到,因为有可能在其他逻辑操作的时候就直接退出该函数了,那么这个时候可以马后炮的说,前面的计算就是浪费的,假设multi是一个很耗性能的计算函数,那么对程序的性能影响不得不考虑。

上面这种符合思维常规的编码方式称为贪婪计算又称为即时计算或者即时求值

即时求值相对应的一种编码方式称之为惰性求值

惰性求值

顾名思义,惰性求值就是将计算延迟至使用的那一刻

仍然以前面的代码为例,如果使用惰性计算,乘法操作会被延迟到需要计算结果的时候才执行。例如,result 变量在实际打印之前不会持有最终的计算结果,而是持有一个包含所有操作的函数,这个函数会在真正需要时逐步执行计算,这就是惰性求值的核心原理。这样可以通过避免不必要的计算来提升性能。

嗯,也许很多人会觉得不一定真的需要使用这种方式,比如可以把求值放到最后需要的时刻,比如在最后使用的时候才调用函数,emm,这种可以,但是我们不能保证每次编码都遵循这种原则,毕竟总有偷懒或者遗忘的过程~

好了,废话不多说,我们开始吧。

既然提到了惰性或者延迟,就不得不想到模板,很多时候,可以使用模板来实现此功能,仍然以前面的为例,用模板实现如下:

template <typename T1,typename T2>
structlazy_t;// #1

template<typename T1,typename T2>// #2
lazy_t<T1, T2> multiply(T1 x, T2 y) {
returnlazy_t<T1, T2>(, y);// #3
}

在上面的代码中:

在#1处声明了一个中间模板类lazy_t,前面的乘法操作将在其内部实现,并在最终结果需要时才执行这些操作。这个类将作为惰性求值的核心类,所有操作都将通过这个类来处理我们希望将多个乘法操作组合起来,multiply 函数的参数可以是整数类型,也可以是之前乘法操作中生成的 lazy_t 类实例,这些实例保存了其他的乘法操作。因此,multiply 函数必须能够接受不同类型的参数。为了实现这一点,实现了 multiply 函数,并使用了两个不同的模板类型来处理不同的参数类型。multiply 函数可以简单地返回 lazy_t<T1, T2> 类,该类存储了两个参数的类型信息。结果类所持有的类型信息和数值将用于通过组合所有给定的操作来计算最终结果

好了,接着来看看核心部分的实现:

template <typename T1,typename T2>
structlazy_t{
lazy_t(const T1& first,
const T2& second)
:first_(first)
,second_(second){}

template<typenameresult_t>
operator result_t() const {
return first_ * second_;// #2
}
private:
  T1 first_;// #1
  T2 second_;// #1
};

在上面这个实现中,我们将乘法的两个变量first和second存储起来,然后定义函数result_t()来获取其结果。

整体用法如下所示:

#include <iostream>

template<typename T1,typename T2>
structlazy_t;// #1

template<typename T1,typename T2>// #2
lazy_t<T1, T2> multiply(T1 x, T2 y) {
returnlazy_t<T1, T2>(, y);// #3
}

template<typename T1,typename T2>
structlazy_t{
lazy_t(const T1& first,
const T2& second)
:first_(first)
,second_(second){}

template<typenameresult_t>
operator result_t() const {
return first_ * second_;// #2
}
private:
  T1 first_;// #1
  T2 second_;// #1
};

int main() {
int x{1}, y{2}, z{3};
auto&&x_y   =multiply(x, y);
auto&&x_y_z =multiply(x_y, z);

int xy = x_y;
int xyz = x_y_z;
  std::cout << xy <<" "<< xyz << std::endl;

return0;
}

尝试编译,报错如下:

<source>: In instantiation of 'lazy_t<T1T2>::operator result_t() const [with result_t = int; T1 = lazy_t<intint>; T2 = int]':
<source>:33:13:   required from here
   33 |   int xyz = x_y_z;
      |             ^~~~~
<source>:20:19: error: no match for 'operator*' (operand types are 'const lazy_t<intint>' and 'const int')
   20 |     return first_ * second_;      // #2
      |            ~~~~~~~^~~~~~~~~

从上面错误输出可以看出,operator result_t()不支持类型为const lazy_t<int, int>' and 'const int'进行操作,为了解决此错误,可以引入一个另外的函数,完整代码如下:

#include <iostream>

template<typename T1,typename T2>
structlazy_t;// #1

template<typename T1,typename T2>// #2
lazy_t<T1, T2> multiply(T1 x, T2 y) {
returnlazy_t<T1, T2>(, y);// #3
}

template<typename T1,typename T2>
structlazy_t{
lazy_t(const T1& first,
const T2& second)
:first_(first)
,second_(second){}

template<typenameresult_t>
operator result_t() const {
return first_ * second_;// #2
}
private:

template<typenameresult_t,typename U1,typename U2>
friendresult_toperator*(constlazy_t<U1, U2>& oth,constresult_t& left
);
  T1 first_;// #1
  T2 second_;// #1
};

template<typenameresult_t,typename U1,typename U2>
result_toperator*(constlazy_t<U1, U2>& oth,constresult_t& left
){
return left * oth.first_ * oth.second_;
}

int main() {
int x{1}, y{2}, z{3};
auto&&x_y   =multiply(x, y);
auto&&x_y_z =multiply(x_y, z);

int xy = x_y;
int xyz = x_y_z;
  std::cout << xy <<" "<< xyz << std::endl;

return0;
}

编译&运行,结果正常~

其实,上面实现基本上满足了我们本文的目的惰性求值,不过,上面这种实现与我们想要的结果耦合太紧,比如上面实现方式只支持乘法操作,如果要一个加法操作的,显然还得实现另外一个~

为此,我们借助于std::function<>来实现此功能,基本代码如下:

#include <iostream>
#include<memory>
#include<functional>

template<typename T>
structLazy{
Lazy(){};
template<typename Func, typename ...Args>
    Lazy(Func& f, Args&&... args) 
{
        func_ =[&f,&args...]{returnf(args...);};
}

operator ()(){
returnfunc_();
}

private:
    std::function<T()> func_;
};

template<classFunc,typename...Args>
Lazy<typename std::result_of<Func(Args...)>::type>lazy(Func&& fun,Args&&...args){
returnLazy<typename std::result_of<Func(Args...)>::type>(std::forward<Func>(fun), std::forward<Args>(args)...);
}

int main() {
auto&&lmd =[](auto x,auto y){return x * y;};

auto&&lz =lazy(lmd,123,456);
std::cout <<lz()<< std::endl;

return0;
}

嗯,代码还是很简单的,在此不再赘述。

如果对本文有异议或者有其他技术问题,可以加微信交流:

推荐阅读  点击标题可跳转

1、C++采坑系列之父子传参

2、一招解决大部分的程序崩溃

3、完美转发不一定完美


雨乐聊编程
毕业于中国科学技术大学,现任某互联网公司高级技术专家一职。本公众号专注于技术交流,分享日常有意思的技术点、线上问题等,欢迎关注