Postfix评估非常慢 - 优化?

时间:2017-10-17 09:50:50

标签: c++ postfix-notation

我写过一个小型表达式评估员。首先,表达式被分解为后缀表示法。让我们使用以下示例:

infix =" z * z + c"

我的代码以下列形式创建字符串向量:z,z,*,c,+

这个问题不是关于Shunting Yard本身,而是关于评估这个后缀的事情。我创建了另一个函数:

Complex Evaluate(vector<string> post, Complex z, Complex c)
{
    vector<Complex> stack;
    Complex a1, a2, a3;
    for (int i = 0; i < post.size(); i++)
    {
        string s = post[i];
        if (IsNumber(s))
        {
            stack.push_back(stold(s));
        }
        else if (IsConst(s))
        {
            if (s == "pi")
            {
                stack.push_back(Pi);
            }
            else if (s == "e")
            {
                stack.push_back(E);
            }
            else if (s == "phi")
            {
                stack.push_back(2.61803398875);
            }
            else if (s == "eulergamma")
            {
                stack.push_back(EulerGamma);
            }
            else if (s == "i")
            {
                stack.push_back(I);
            }
        }
        else if (IsOperator(s))
        {
            if (s == "+")
            {
                a1 = stack[stack.size() - 1];
                a2 = stack[stack.size() - 2];
                stack.pop_back(); stack.pop_back();
                stack.push_back(a1 + a2);
            }
            else if (s == "-")
            {
                a1 = stack[stack.size() - 1];
                a2 = stack[stack.size() - 2];
                stack.pop_back(); stack.pop_back();
                stack.push_back(a2 - a1);
            }
            else if (s == "*")
            {
                a1 = stack[stack.size() - 1];
                a2 = stack[stack.size() - 2];
                stack.pop_back(); stack.pop_back();
                stack.push_back(a1 * a2);
            }
            else if (s == "/")
            {
                a1 = stack[stack.size() - 1];
                a2 = stack[stack.size() - 2];
                stack.pop_back(); stack.pop_back();
                stack.push_back(a2 / a1);
            }
            else if (s == "^")
            {
                a1 = stack[stack.size() - 1];
                a2 = stack[stack.size() - 2];
                stack.pop_back(); stack.pop_back();
                stack.push_back(pow(a2, a1));
            }
        }
        else if (IsFunction(s))
        {
            if (s == "sqrt")
            {
                stack[stack.size() - 1] = sqrt(stack[stack.size() - 1]);
            }
            else if (s == "sin")
            {
                stack[stack.size() - 1] = sin(stack[stack.size() - 1]);
            }
            else if (s == "arcsin" || s == "asin")
            {
                stack[stack.size() - 1] = asin(stack[stack.size() - 1]);
            }
            else if (s == "sinc")
            {
                stack[stack.size() - 1] = sinc(stack[stack.size() - 1]);
            }
            else if (s == "cos")
            {
                stack[stack.size() - 1] = cos(stack[stack.size() - 1]);
            }
            else if (s == "cosc")
            {
                stack[stack.size() - 1] = cosc(stack[stack.size() - 1]);
            }
            else if (s == "arccos" || s == "acos")
            {
                stack[stack.size() - 1] = acos(stack[stack.size() - 1]);
            }
            else if (s == "tan" || s == "tg")
            {
                stack[stack.size() - 1] = tan(stack[stack.size() - 1]);
            }
            else if (s == "arctan" || s == "atan")
            {
                stack[stack.size() - 1] = atan(stack[stack.size() - 1]);
            }
            else if (s == "cot" || s == "cotg" || s == "cotan")
            {
                stack[stack.size() - 1] = cot(stack[stack.size() - 1]);
            }
            else if (s == "exp")
            {
                stack[stack.size() - 1] = exp(stack[stack.size() - 1]);
            }
            else if (s == "log" || s == "ln" || s == "lg")
            {
                stack[stack.size() - 1] = log(stack[stack.size() - 1]);
            }
            else if (s == "log10")
            {
                stack[stack.size() - 1] = log(stack[stack.size() - 1]) / Complex(log(10), 0);
            }
            else if (s == "log2")
            {
                stack[stack.size() - 1] = log(stack[stack.size() - 1]) / Complex(log(2), 0);
            }
            else if (s == "sinh" || s == "sh")
            {
                stack[stack.size() - 1] = sinh(stack[stack.size() - 1]);
            }
            else if (s == "arcsinh" || s == "asinh" || s == "ash")
            {
                stack[stack.size() - 1] = arcsinh(stack[stack.size() - 1]);
            }
            else if (s == "cosh" || s == "ch")
            {
                stack[stack.size() - 1] = cosh(stack[stack.size() - 1]);
            }
            else if (s == "arccosh" || s == "acosh" || s == "ach")
            {
                stack[stack.size() - 1] = arccosh(stack[stack.size() - 1]);
            }
            else if (s == "tanh" || s == "th" || s == "tgh")
            {
                stack[stack.size() - 1] = tanh(stack[stack.size() - 1]);
            }
            else if (s == "arctanh" || s == "atanh" || s == "ath")
            {
                stack[stack.size() - 1] = arctanh(stack[stack.size() - 1]);
            }
            else if (s == "cotanh" || s == "coth" || s == "ctgh" || s == "cth")
            {
                stack[stack.size() - 1] = coth(stack[stack.size() - 1]);
            }
            else if (s == "arccoth" || s == "acoth" || s == "acth")
            {
                stack[stack.size() - 1] = arccoth(stack[stack.size() - 1]);
            }
            else if (s == "lgamma" || s == "loggamma" || s == "logamma")
            {
                stack[stack.size() - 1] = lgamma(stack[stack.size() - 1], max(100000, (int)round(100000 * (abs(stack[stack.size() - 1]).Re))));
            }
        }
        else if (IsParam(s))
        {
            if (s == "z") { stack.push_back(z); }
            else if (s == "c") { stack.push_back(c); }
        }
    }
    return stack[stack.size() - 1];
}

所以,如果我调用Evaluate(后缀,4,2),我得到正确的4 * 4 + 2 = 18.但是,我需要迭代许多复杂的点(第三个参数,c),所以我需要执行这个很快。当我执行以下操作(测试速度)时:

for (int i = 0; i < 500000; i++)
{
    if ((i % 1000) == 0) { cout << i << endl; }
    result = Evaluate(postfix, 4, 2);
}

完成需要几十秒,这非常非常慢。什么是快速实现后缀表达式评估的典型方法?为了完整起见,我还包括Complex.h和Complex.cpp:

#pragma once
#ifndef COMPLEX_H
#define COMPLEX_H

class Complex
{
public:
    long double Re, Im;
    Complex(long double re = 0., long double im = 0.);
    friend Complex operator+(Complex, Complex);
    friend Complex operator+(long double, Complex);
    friend Complex operator+(Complex, long double);
    friend Complex operator-(Complex, Complex);
    friend Complex operator-(long double, Complex);
    friend Complex operator-(Complex, long double);
    Complex operator-() const &;
    friend Complex operator*(Complex, Complex);
    friend Complex operator*(long double, Complex);
    friend Complex operator*(Complex, long double);
    friend Complex operator/(Complex, Complex);
    friend Complex operator/(long double, Complex);
    friend Complex operator/(Complex, long double);
};

Complex operator+ (Complex c1, Complex c2);
Complex operator+ (long double r, Complex c);
Complex operator+ (Complex c, long double r);
Complex operator- (Complex c1, Complex c2);
Complex operator- (long double r, Complex c);
Complex operator- (Complex c, long double r);
Complex operator* (Complex c1, Complex c2);
Complex operator* (long double r, Complex c);
Complex operator* (Complex c, long double r);
Complex operator/ (Complex c1, Complex c2);
Complex operator/ (long double r, Complex c);
Complex operator/ (Complex c, long double r);

Complex arg(Complex c);
Complex abs(Complex c);
Complex sqrt(Complex c);
Complex re(Complex c);
Complex im(Complex c);
Complex cc(Complex c);
Complex exp(Complex c);
Complex log(Complex c);
Complex sinh(Complex c);
Complex arcsinh(Complex c);
Complex cosh(Complex c);
Complex arccosh(Complex c);
Complex tanh(Complex c);
Complex arctanh(Complex c);
Complex coth(Complex c);
Complex arccoth(Complex c);
Complex pow(Complex c, int n);
Complex pow(Complex c1, Complex c2);
Complex sin(Complex c);
Complex asin(Complex c);
Complex sinc(Complex c);
Complex cos(Complex c);
Complex acos(Complex c);
Complex cosc(Complex c);
Complex tan(Complex c);
Complex atan(Complex c);
Complex cot(Complex c);
Complex acot(Complex c);
Complex lgamma(Complex c, int n);

#endif

#include "stdafx.h"
#include "stdio.h"
#include <iostream>
#include "Complex.h"

const long double EulerGamma = 0.57721566490153286060651209008240243104215933593992;
const Complex I = Complex(0, 1);

Complex::Complex(long double r, long double i)
{
    Re = r; Im = i;
}

Complex operator+ (Complex c1, Complex c2)
{
    return Complex(c1.Re + c2.Re, c1.Im + c2.Im);
}

Complex operator+ (long double r, Complex c)
{
    return Complex(r + c.Re, c.Im);
}

Complex operator+ (Complex c, long double r)
{
    return Complex(r + c.Re, c.Im);
}

Complex operator- (Complex c1, Complex c2)
{
    return Complex(c1.Re - c2.Re, c1.Im - c2.Im);
}

Complex operator- (long double r, Complex c)
{
    return Complex(r - c.Re, -c.Im);
}

Complex Complex::operator-() const &
{
    return Complex(- this->Re, - this->Im);
}

Complex operator- (Complex c, long double r)
{
    return Complex(c.Re - r, c.Im);
}

Complex operator* (Complex c1, Complex c2)
{
    Complex result;
    result.Re = (c1.Re * c2.Re - c1.Im * c2.Im);
    result.Im = (c1.Re * c2.Im + c1.Im * c2.Re);
    return result;
}

Complex operator* (long double r, Complex c)
{
    return Complex(r*c.Re, r*c.Im);
}

Complex operator* (Complex c, long double r)
{
    return Complex(r*c.Re, r*c.Im);
}

Complex operator/ (Complex c1, Complex c2)
{
    Complex result;
    result.Re = ((c1.Re * c2.Re + c1.Im * c2.Im) / (c2.Re*c2.Re + c2.Im*c2.Im));
    result.Im = ((c1.Im * c2.Re - c1.Re * c2.Im) / (c2.Re*c2.Re + c2.Im*c2.Im));
    return result;
}

Complex operator/ (long double r, Complex c)
{
    Complex result;
    result.Re = (r * c.Re / (c.Re*c.Re + c.Im*c.Im));
    result.Im = (-r * c.Im / (c.Re*c.Re + c.Im*c.Im));
    return result;
}

Complex operator/ (Complex c, long double r)
{
    return Complex(c.Re / r, c.Im / r);
}

Complex abs(Complex c)
{
    return Complex(sqrt(c.Re*c.Re + c.Im*c.Im), 0);
}

Complex arg(Complex c)
{
    return Complex(atan2(c.Im, c.Re), 0);
}

Complex sqrt(Complex c)
{
    long double r = abs(c).Re;
    long double phi = arg(c).Re;
    return Complex(sqrt(r)*cos(0.5*phi), sqrt(r)*sin(0.5*phi));
}

Complex re(Complex c)
{
    return Complex(c.Re, 0);
}

Complex im(Complex c)
{
    return Complex(0, c.Im);
}

Complex cc(Complex c)
{
    return Complex(c.Re, -c.Im);
}

Complex exp(Complex c)
{
    long double ex = exp(c.Re);
    return ex * Complex(cos(c.Im), sin(c.Im));
}

Complex log(Complex c)
{
    long double r = abs(c).Re;
    long double phi = atan2(c.Im, c.Re);
    return Complex(log(r), phi);
}

Complex sinh(Complex c)
{
    return Complex(sinh(c.Re)*cos(c.Im), cosh(c.Re)*sin(c.Im));
}

Complex arcsinh(Complex c)
{
    return log(c + sqrt(1 + c*c));
}

Complex cosh(Complex c)
{
    return Complex(cosh(c.Re)*cos(c.Im), sinh(c.Re)*sin(c.Im));
}

Complex arccosh(Complex c)
{
    return log(c + sqrt(c + 1)*sqrt(c - 1));
}

Complex tanh(Complex c)
{
    return sinh(c) / cosh(c);
}

Complex arctanh(Complex c)
{
    return 0.5*(log(1.0 + c) - log(1.0 - c));
}

Complex coth(Complex c)
{
    return cosh(c) / sinh(c);
}

Complex arccoth(Complex c)
{
    return 0.5*(log(1 + 1 / c) - log(1 - 1 / c));
}

Complex pow(Complex c, int n)
{
    if (n == 0)
    {
        return Complex(1, 0);
    }
    else if (n == 1)
    {
        return c;
    }
    else if (n > 0)
    {
        return c*pow(c, n - 1);
    }
    else
    {
        return Complex(1, 0) / pow(c, -n);
    }
}

Complex pow(Complex c1, Complex c2)
{
    if (abs(c2.Re - round(c2.Re)) > 1e-12 || c2.Im != 0)
    {
        return exp(c2*log(c1));
    }
    else
    {
        int n = round(c2.Re);
        return pow(c1, n);
    }
}

Complex sin(Complex c)
{
    return Complex(sin(c.Re)*cosh(c.Im), cos(c.Re)*sinh(c.Im));
}

Complex asin(Complex c)
{
    return -I*log(I*c + sqrt(abs(1 - c*c)) * exp(0.5*I*arg(1 - c*c)));
}

Complex sinc(Complex c)
{
    if (abs(c).Re > 0.01) { return sin(c) / c; }
    else { return 1 - c*c / 6 + c*c*c*c / 120; }
}

Complex cos(Complex c)
{
    return Complex(cos(c.Re)*cosh(c.Im), -sin(c.Re)*sinh(c.Im));
}

Complex acos(Complex c)
{
    return -I*log(c + I*sqrt(abs(1 - c*c)) * exp(0.5*I*arg(1 - c*c)));
}

Complex cosc(Complex c)
{
    if (abs(c).Re > 0.01) { return (1 - cos(c)) / c; }
    else { return c / 2 - c*c*c / 24 + c*c*c*c*c / 720; }
}

Complex tan(Complex c)
{
    return sin(c) / cos(c);
}

Complex atan(Complex c)
{
    return (1 / (2 * I))*log((I - c) / (I + c));
}

Complex cot(Complex c)
{
    return cos(c) / sin(c);
}

Complex acot(Complex c)
{
    return (1 / (2*I))*log((c + I) / (c - I));
}

Complex lgamma(Complex c, int n)
{
    Complex res = 0;
    res = -EulerGamma*c - log(c);
    for (int i = 1; i <= n; i++)
    {
        res = res + c / i - log(1 + c / i);
    }
    return res;
}

像IsOperator这样的其他功能是:

const vector<string> delimiters = { "(", ")", "[", "]", "{", "}" };
const vector<string> operators = { "+", "-", "/", "*", "^", "%" };
const vector<string> separators = { ",", ";", ":" };
const vector<string> params = { "x", "y", "z", "a", "b", "c", "w" };
const vector<string> constants = { "pi", "e", "eulergamma", "phi", "i" };
const Complex I = Complex(0, 1);
const long double EulerGamma = 0.57721566490153286060651209008240243104215933593992;
const long double Pi = 3.14159265358979323846264338327950288419716939937510;
const long double E = 2.71828182845904523536028747135266249775724709369995;

bool IsOperator(string isop)
{
    if (find(operators.begin(), operators.end(), isop) != operators.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool IsParam(string ispar)
{
    if (find(params.begin(), params.end(), ispar) != params.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool IsDelim(string isdel)
{
    if (find(delimiters.begin(), delimiters.end(), isdel) != delimiters.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool IsConst(string iscon)
{
    if (find(constants.begin(), constants.end(), iscon) != constants.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool IsSeparator(string issep)
{
    if (find(separators.begin(), separators.end(), issep) != separators.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool LeftPar(string isleftpar)
{
    if (isleftpar == "(" || isleftpar == "[" || isleftpar == "{") { return true; }
    else { return false; }
}

bool RightPar(string isrightpar)
{
    if (isrightpar == ")" || isrightpar == "]" || isrightpar == "}") { return true; }
    else { return false; }
}

bool IsFunction(string isfun)
{
    if (!IsNumber(isfun) && !IsParam(isfun) && !IsConst(isfun) && !IsDelim(isfun) && !IsSeparator(isfun) && !IsOperator(isfun))
    {
        return true;
    }
    else
    {
        return false;
    }
}

基本上,我的问题是:为什么需要这么长时间?瓶颈在哪里?除了一堆if else(代码必须以某种方式识别数字,变量,函数等)之外,还能如何实现呢?我不需要你运行这段代码,我只是希望有人看看它并指出可以做得更好的事情(除了为这类事情导入外部库)。感谢。

P.S。:我想使用sin,cos,exp,pow等函数,所以我必须拥有许多函数的第二部分。但据我所知,if else(IsFunction)如果事先发现了它的z,c或数字,就不会执行。

3 个答案:

答案 0 :(得分:3)

没有必要调用IsOperator然后依次与每个运算符进行比较。问题是,目前,您正在进行巨大的数量的字符串比较。为每个函数,常量和运算符创建一组函数。有一个std::unordered_map从字符串映射到适当的函数。在地图中查看该功能,然后调用该功能。

类似的东西:

typedef void (*function)(std::stack<Complex>& stack, const Complex& z,
                                                     const Complex& c);

void plus(std::stack<Complex>& stack, const Complex& , const Complex& )
{
    const auto a1 = stack.top();
    stack.pop();
    const auto a2 = stack.top();
    stack.pop();
    stack.push( a1+a2 );
}

const static std::unordered_map<std::string, function> lookup_table
    { {"+",plus}, ... };

Complex Evaluate(const std::vector<string>& post, const Complex& z,
                                                  const Complex& c)
{
    std::stack<Complex> stack;
    for (const auto& s : post)
    {
        const auto it = lookup_table.find(s);
        if (it != lookup_table.end())
        {
            (it->second)(stack, z, c);
        }
        else if (IsNumber(s))
        {
            stack.push_back(stold(s));
        }
        else
        {
            // bad input
        }
     }
     ....

你可以通过创建一个保存堆栈和参数的Evaluator类,然后使函数成为其成员函数,使其变得有点光滑。然后你的地图将成为指向成员函数的指针,你将使用(evaluator->*(it-second))()进行调用。

请注意,我还切换到使用std::stack,并通过引用传递所有内容。这对post参数特别有帮助。

答案 1 :(得分:2)

Complex Evaluate(vector<string> post, Complex z, Complex c)
{

所有这些参数都按值传递。这意味着每次调用Evaluate()都会复制整个post向量,无论如何都没有用。整个矢量。向量中的每个字符串。此外,两个Complex类也会重复。这完全是对电子的浪费。

一个良好的开端是通过引用传递所有参数:

Complex Evaluate(const vector<string> &post, const Complex &z, const Complex &c)

现在让我们来看看循环。

for (int i = 0; i < post.size(); i++)
{
    string s = post[i];

更现代的编译器可能会优化对size()的调用,而不必在每次迭代时调用它,方法是正确分析post永远不会更改。不太可能的是编译器足够聪明,可以完全优化掉复制结构。

此代码效率低下的一个常见根本原因是您需要更好地了解对象在C ++中的工作方式。这是一个复制构建一个全新的对象,没有任何充分的理由这样做。甚至,充其量也是潜在的电子浪费。

for (const auto &s:post)

现在,编译器拥有生成最有效的循环代码所需的全部内容,并对循环内的当前元素使用别名引用,罚分为0%。

           a1 = stack[stack.size() - 1];
           a2 = stack[stack.size() - 2];

这两个对象在函数的开头声明。每次,在这里,赋值运算符都需要复制现有对象(来自stack)并将其放入a1(和a2)。同样,没有任何有用的东西来自那。我估计编译器可以理解a1a2的初始声明是浪费的低概率机会,并且可以在这里优化赋值运算符。而不是声明a1a2

           auto e=stack.end();
           const auto &a1 = *--e;
           const auto &a2 = *--e;

这应该足以让编译器通过简单计算两个内存地址来替换所有浪费的电子,从无用地复制内存中的对象。

        else if (s == "phi")

        else if (s == "eulergamma")

        if (s == "sqrt")

        else if (s == "sin")

一公吨的字符串比较。

所有这些应该由静态unordered_map查找表替换,将关键字映射到lambdas并预先正确初始化。 lambdas采用一些参数,例如stack,并执行必要的计算。你必须做IsNumber()等等,作为第一项业务,我认为这将是最常见的命中,如果失败,则回到使用查找映射为剩余的业务。

这将取代执行公吨字符串比较的大量电子浪费,使用单个散列查找,以及通过函数指针进行间接调用。

答案 2 :(得分:1)

正如user16320所说,效率低下的可能原因是复制参数 - 你到处都这样做,包括评估post,z和c是副本,然后调用cos()和运算符都复制复杂的对象。虽然不大,但创造和摧毁它们会减慢一切。使用常数refs而不是经过Vale。使用分析器检查这是问题所在。