C++ Exprtk 与 Python eval()

Posted

技术标签:

【中文标题】C++ Exprtk 与 Python eval()【英文标题】:C++ Exprtk vs Python eval() 【发布时间】:2017-07-21 16:41:39 【问题描述】:

问题如下。 一个文本文件包含数百万行算术 - 需要快速评估。 我一直在探索解决这个问题的选项,并使用漂亮的 exprtk C++ 库编写了一个小脚本。 该代码有效并且能够评估表达式,但比我想象的要慢。算术行可能会变得很长,这可能会使问题更加复杂。出于兴趣,我将评估时间与基本 Python eval() 命令的评估时间进行了比较,很惊讶eval() 比 exprtk 快 3-4 倍!

这里是C++ 代码:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <sstream>
#include <string>

#include "exprtk.hpp"

int main()

    typedef exprtk::symbol_table<double> symbol_table_t;
    typedef exprtk::expression<double> expression_t;
    typedef exprtk::parser<double> parser_t;
    typedef exprtk::parser_error::type error_t;

    // Define variables in strings
    double A = 1.1;
    double B = 2.2;
    double C = 3.3;
    double m3 = 1.0;
    double z3 = 2.0;

    symbol_table_t symbol_table;
    symbol_table.add_constants();
    symbol_table.add_variable("A", A);
    symbol_table.add_variable("B", B);
    symbol_table.add_variable("C", C);
    symbol_table.add_variable("m3", m3);
    symbol_table.add_variable("z3", z3);

    expression_t expression;
    expression.register_symbol_table(symbol_table);

    parser_t parser;
    // Load the text file and loop over the lines
    std::ifstream fin("test.txt");
    std::string file_line;
    while(std::getline(fin, file_line)) 
        // current line of text is in file_line, not including the \n
        std::string expression_str = file_line;

        if(!parser.compile(expression_str, expression)) 
            printf("Error: %s\tExpression: %s\n", parser.error().c_str(), expression_str.c_str());

            for(std::size_t i = 0; i < parser.error_count(); ++i) 
                const error_t error = parser.get_error(i);
                printf("Error: %02d Position: %02d Type: [%s] Msg: %s Expr: %s\n",
                       static_cast<int>(i),
                       static_cast<int>(error.token.position),
                       exprtk::parser_error::to_str(error.mode).c_str(),
                       error.diagnostic.c_str(),
                       expression_str.c_str());
            

            return 1;
        

        double result = expression.value();

        printf("%10.16f\n", result);
    
    return 0;
 

这是 Python 代码:

with open("test.txt", 'r') as h:
    linesHH = h.readlines()

// Apply list comprehension
matt = [eval(x) for x in linesHH]

文本文件有一个非常基本的格式,一个新的算术行紧随其后。第一行的例子如下:

-10./(A+B)^4-2.500000000000000*A^3/C^3/(A+B)^4-9.250000000000000*A/C/(A+B)^4-2.500000000000000*B^3/C^3/(A+B)^4-9.250000000000000*B/C/(A+B)^4-8.*A^2/C^3/(A+B)^4-8.*B^2/C^3/(A+B)^4-1.750000000000000*A/B/(A+B)^4+2.250000000000000*B^2/C^2/(A+B)^4-1.750000000000000/A*B/(A+B)^4-1./A^2*B^2/(A+B)^4-.2500000000000000/A^3*B^3/(A+B)^4-13.*A/C^2/(A+B)^4-13.*B/C^2/(A+B)^4-.2500000000000000*A^3/B^3/(A+B)^4+2.250000000000000*A^2/C^2/(A+B)^4-1.*A^2/B^2/(A+B)^4+62./C/(A+B)^4*z3-11./C/(A+B)^4-13.*A^2*B/C^3/(A+B)^4+3.500000000000000*A^2/B/C/(A+B)^4-13.*A*B^2/C^3/(A+B)^4+3.500000000000000/A*B^2/C/(A+B)^4-14.*A*B/C^3/(A+B)^4-.5000000000000000/A*B^4/C^3/(A+B)^4-1./A*B^3/C^2/(A+B)^4-.2500000000000000/A^2*B^4/C^2/(A+B)^4-2./A^2*B^3/C/(A+B)^4-.2500000000000000/A^3*B^4/C/(A+B)^4-1.*A^3/B/C^3/(A+B)^4-.5000000000000000*A^3/B^2/C^2/(A+B)^4-2.500000000000000*A^2/B/C^2/(A+B)^4-.5000000000000000*A^2/B^2/C/(A+B)^4-2.*A/B/C/(A+B)^4-1./A*B^3/C^3/(A+B)^4-2.500000000000000/A*B^2/C^2/(A+B)^4-2./A*B/C/(A+B)^4-.5000000000000000/A^2*B^3/C^2/(A+B)^4-.5000000000000000/A^2*B^2/C/(A+B)^4-.5000000000000000*A^4/B/C^3/(A+B)^4-.2500000000000000*A^4/B^2/C^2/(A+B)^4-.2500000000000000*A^4/B^3/C/(A+B)^4-1.*A^3/B/C^2/(A+B)^4-2.*A^3/B^2/C/(A+B)^4-18.*A*B/C^2/(A+B)^4+26.*A/C^2/(A+B)^4*z3+26.*B/C^2/(A+B)^4*z3+11.*A/B/C/(A+B)^4*z3+5./A*B^2/C^2/(A+B)^4*z3+11./A*B/C/(A+B)^4*z3+1/A^2*B^3/C^2/(A+B)^4*z3+5./A^2*B^2/C/(A+B)^4*z3+1/A^3*B^3/C/(A+B)^4*z3+A^3/B^2/C^2/(A+B)^4*z3+A^3/B^3/C/(A+B)^4*z3+5.*A^2/B/C^2/(A+B)^4*z3+5.*A^2/B^2/C/(A+B)^4*z3

我应该对此感到惊讶吗? 我很惊讶,因为阅读文档强调 eval() 很慢并且通常应该避免(主要是由于其固有的安全问题),但在这个特定示例中,它似乎比我的代码执行得更好。

我不相信 exprtk 是线程安全的,所以我怀疑多线程有很多选择。

为了加快速度,可以使用调车场算法和反向抛光符号,但仅从这个比较中,我就对C++Python 之间的速度差异感到惊讶。这种速度差异有明显的原因吗?exprtk 中是否有更多开销,还是我的代码完全是垃圾?应该是后者……

编辑

我使用 exprtk 库的原因是阅读 this 数学解析器基准测试。

【问题讨论】:

您是否在启用优化的情况下进行编译? @BenSteffan 我使用 -O2 编译器标志编译。 ^ 在 Python 中是 XOR。此外,您的时间可能更多是关于磁盘访问而不是表达式解析。 您是否也对 python 的答案错误感到惊讶? @JimR 如果您指的是字符串中的 ^ 运算符,则对于 python 脚本,它已替换为 **。 【参考方案1】:

如post 中所述,在 C++ 中天真地读取行数比 Python 慢得多,要使用 C++ 获得更快的结果,请使用上述帖子中建议的方法之一来加快读取行数

【讨论】:

以上是关于C++ Exprtk 与 Python eval()的主要内容,如果未能解决你的问题,请参考以下文章

使用 python 的 eval() 与 ast.literal_eval()

snapde的批量数据运算公式

Python eval 与 exec 函数区别

使用 TF-slim 训练的模型与 python 推理完美配合,但使用 C++ 给出完全错误的结果

eval()函数与int()函数的区别

eval()函数与int()函数的区别