C++ g++;优化:O2标志修复了O3再次破坏的代码

C++ g++;优化:O2标志修复了O3再次破坏的代码,c++,g++,compiler-optimization,C++,G++,Compiler Optimization,这段代码,用于匹配NFA中的字符串,我认为需要O(N^2)内存,当字符串大小为20000时,可以预料会中断,然后使用-O2编译代码,然后再次中断-O3。编译是在启用了-std=c++14的情况下完成的。在我看来,问题在于堆栈溢出 输入字符串被“ab”重复10000次,最后加上一个'c'。下图包含我试图匹配的NFA 具体而言,我的问题是: 1) 这(我相信这是令人印象深刻的)修复背后的优化是什么 2) 而什么-O3优化再次破坏了它 struct State { map<char,ve

这段代码,用于匹配NFA中的字符串,我认为需要
O(N^2)
内存,当字符串大小为
20000
时,可以预料会中断,然后使用
-O2
编译代码,然后再次中断
-O3
。编译是在启用了
-std=c++14
的情况下完成的。在我看来,问题在于堆栈溢出

输入字符串被
“ab”
重复
10000次,最后加上一个
'c'
。下图包含我试图匹配的NFA

具体而言,我的问题是:

1) 这(我相信这是令人印象深刻的)修复背后的优化是什么

2) 而什么
-O3
优化再次破坏了它

struct State
{
    map<char,vector<State*> > transitions;
    bool accepting = false;
};

bool match(State* state,string inp){
    if(inp=="") return state->accepting;

    for(auto s:state->transitions[inp[0]]) 
        if(match(s,inp.substr(1))) return true;

    for(auto s:state->transitions['|']) //e-transitions
        if(match(s,inp)) return true;

    return false;
}
struct状态
{
地图转换;
布尔接受=假;
};
布尔匹配(状态*状态,字符串输入){
if(inp==“”)返回状态->接受;
用于(自动:状态->转换[inp[0]])
if(match(s,inp.substr(1)))返回true;
对于(自动s:state->transitions['|'])//e-transitions
if(match(s,inp))返回true;
返回false;
}
在gcc文档中,据说O3具有O2的所有优化,以及更多优化。我无法“理解”这些额外的内容或它们与这个问题的相关性。我想强调,就我在类似问题中所看到的,我不是在寻找解决这个问题的具体方法


正如您已经发现的:问题在于递归的堆栈使用。同样,无论是对
-O2
还是对
-O3
,都不会执行TLO(理论上,只有最后一次重复调用才可能执行TLO,这对您的情况没有帮助)

但是,根据优化的级别,函数在堆栈上需要不同的空间量。无法保证
-O3
版本会更快,并且需要更少的堆栈空间

当我们查看时,我们可以看到以下内容:

  • -O3
    通过
    subq$88,%rsp
    保留88个字节,堆栈上的占用空间甚至更大,因为除了通常的函数序言外,还将寄存器
    r12-r15
    推送到堆栈上

  • -O2
    除了堆栈上推送的寄存器外,只保留56个字节

  • 在没有优化的情况下,堆栈上的内存占用是最大的:需要在两行原始代码之间的堆栈中存储/加载所有内容,以便获得可预测的调试行为,以便我们可以在调试器中更改值

  • 这可以解释你的观察结果:没有优化,堆栈很快就满了<代码>-O2
    优化减轻了它(但没有修复它),因此可以处理20000的递归深度-它可能会崩溃30000次
    -O3
    优化具有更大的堆栈占用空间,并且对于较小的输入已经失败

    解决这个问题的正确方法现在显而易见:应该使用深度优先搜索的迭代版本或广度优先搜索


    代码中的另一个问题是使用
    substr
    ,这会导致不必要的内存复制/使用。只需将迭代器传递给字符串中的第一个字符,并为递归调用增加它。

    对于长度为20000的字符串,递归调用的数量可能超过堆栈。要证明/反驳这一点,请尝试增加堆栈大小。@RichardCriten,对于10000,效果很好。所以我认为你是对的。然而,我很好奇O2是如何应对这种情况的。你正在运行UB(超过堆栈),改变优化级别可能是在进行尾部递归消除,或者只是看起来起了作用。您应该检查生成的程序集。据我所知,此代码看起来不可优化。我对x86汇编一无所知。这可以解决我认为的问题。你能提供一个例子(nfa,输入字符串生成器),O2工作,O3不工作吗?我应该想到这一点-O3适用于高达19k的字符串,O2在我的电脑中在22k时中断。差距很小。我有3个问题。x86汇编有多难?你从哪里学的?最重要的是,对于在可预见的未来不一定会使用这些知识的人来说,学习x86值得吗?我知道答案会很复杂,但请尝试用布尔语言回答最后一个问题。@Shihabshariar我对x86的理解非常有限,所以我没有资格回答你的前两个问题。至于第三点:汇编使你成为一个更好的程序员,因为它使你能够理解事情的实际工作原理——你不必猜测,编译器做了什么——你只需要看到它