惰性评估有效/可优化吗?

问题描述

我发现懒惰评估有许多用途,例如用于优化的工具(例如matrices)。

另一种用途是句法糖。但是在我付诸实践之前,我以牺牲运行时开销为代价来使代码看起来更加整洁的情况下,编译器是否知道如何优化这种东西?还是只在潜在开销比不使用惰性评估快时才使用它?

以下是我的意思的一个例子。这不是我的实际用例,只是非懒惰与懒惰评估的简单版本。

// original
template < class out,class in >
out && lazy_cast( in && i )
{
    return ( out && )( i );
}

// usage
char c1 = 10;
int c2 = lazy_cast< int >( c1 );
// lazy
template < class in >
class C_lazy_cast
{
public:
    in && i;

    template < class out && >
    operator out &&( )
    {
        return ( out && )( i );
    }
};

template < class in >
C_lazy_cast< in > lazy_cast( in && i )
{
    return { std::forward< in >( i ) };
}

// usage
char c1 = 10;
int c2 = lazy_cast( c1 );

为了完整起见,有关MSVC,GCC和clang的信息应该足够。

解决方法

编译器可以做的比您想像的要多得多。 我不确定您要在示例中使用什么,但是请考虑following piece of code

template <typename TLazyChar>
struct lazyUpperImpl{
    TLazyChar in;
    lazyUpperImpl(TLazyChar in_):in(in_){}
    char constexpr operator()(){
        auto c = in();
        if (c >= 'a' && c <= 'z'){
            c = c - 'a' + 'A';
        }
        return c;
    }
};

template <typename TLazyNumeric>
struct lazyAdd5Impl{
    TLazyNumeric in;
    lazyAdd5Impl(TLazyNumeric in_):in(in_){}
    int constexpr operator()(){
        return in() + 5;
    }
};

template <typename Tout,typename TLazyIn>
struct lazyCastImpl {
    TLazyIn in;
    lazyCastImpl(TLazyIn in_):in(in_){}

    Tout constexpr operator()(){
        return static_cast<Tout>(in());
    }
};

template <typename Tout,typename TLazyIn>
auto constexpr lazyCast(TLazyIn in){
    return lazyCastImpl<Tout,TLazyIn>(in);
}

template <typename TLazyChar>
auto constexpr lazyUpper(TLazyChar in){
    return lazyUpperImpl<TLazyChar>(in);
}

template <typename TLazyNumeric>
auto constexpr lazyAdd5(TLazyNumeric in){
    return lazyAdd5Impl<TLazyNumeric>(in);
}

int foo(int in){
    auto lazyInt = [in](){return in;};
    auto x =
        lazyAdd5(
            lazyCast<int>(
                lazyUpper(
                    lazyCast<char>(lazyInt)
                )
            )
        ) ();

    return x;
}

int main(){
    return foo(109);
}

通过gcc 10.2clang 10.0.1msvc 19.24foo的代码成为一组简单的指令-有条件地减去26,并始终加5。

例如,msvc生成的程序集为:

    movzx   eax,cl
    cmp     cl,97                      ; 00000061H
    jl      SHORT $LN26@foo
    cmp     cl,122                     ; 0000007aH
    jg      SHORT $LN26@foo
    lea     eax,DWORD PTR [rcx-32]
$LN26@foo:
    movsx   eax,al
    add     eax,5
    ret     0

msvc的输出可以说是这三个中最不详尽(因此最容易理解)的。

此外,如果您以编译时已知的输入值开头,则将其内联到单个return new_value指令中。

请注意,在上面的示例中,编译器无法避免出现if条件,而'lazyCast'只是一个空操作。

Here是另一个有趣的示例,其中两个if语句和两个数学表达式被简单地抵消了。

对于真实程序,很难从汇编中预测实际效率,这是不可能的-它取决于执行程序的机器以及该机器所处的状态(例如,它是否同时运行不同的程序?)。即使那样,您仍需要运行测试以确保哪些行效果最好。

当然,最佳是主观的。您需要确定是否更喜欢优化运行时间(可能会进一步细分),可执行文件的大小或功耗要求?

这需要大量的学习才能正确,但是您可以放心,有一些非常有才华的人正在为解决这些问题而谋生,他们通常做得很好。