【问题标题】:How do I optimize this postfix expression tree for speed?如何优化这个后缀表达式树以提高速度?
【发布时间】:2010-05-05 11:44:56
【问题描述】:

感谢我在this 帖子中获得的帮助:

我有一个简洁的递归函数来按后缀顺序遍历树:

deque <char*> d;
void Node::postfix()
{
    if (left != __nullptr) { left->postfix(); } 
    if (right != __nullptr) { right->postfix(); } 
    d.push_front(cargo);
    return;
};

这是一个表达式树。分支节点是从数组中随机选择的算子,叶节点是值或变量“x”,也是从数组中随机选择的。

char *values[10]={"1.0","2.0","3.0","4.0","5.0","6.0","7.0","8.0","9.0","x"};
char *ops[4]={"+","-","*","/"};

由于在运行它所属的遗传算法期间这将被调用数十亿次,因此我想对其进行优化以提高速度。我有很多关于这个主题的问题,我将在单独的帖子中提出。

第一个问题是:我如何才能访问找到的每个“货物”。那就是:我不想将“货物”推到双端队列上,然后处理双端队列以获取值,而是立即开始处理它。

编辑:This 问题表明事后处理双端队列是更好的方法。

我还不知道 c++ 中的并行处理,但理想情况下这将在两个不同的处理器上同时完成。

在 python 中,我将函数设为生成器并使用 .next() 访问后续的“货物”。

参见上面的编辑。

但我正在使用 c++ 来加速 python 实现。我在想这种树已经存在了很长时间,并且可能已经有人对其进行了优化。有任何想法吗?谢谢

【问题讨论】:

    标签: c++ expression-trees parallel-processing


    【解决方案1】:

    当然,在您在这里进行优化之前,您需要先衡量成本开销,因为您的遗传算法下一代生产和突变可能会占用评估时间。

    一旦您确定要优化... 显而易见的答案是编译表达式(“尽可能”)。幸运的是,有很多方法可以“编译”。

    如果你在 Python 中实现这个,你可以让 Python(我不是专家)将一个构造的抽象语法树编译成一个函数,这可能会快很多,特别是如果 CPython 支持这个.

    不过,您似乎是在 C++ 中实现这一点的。在这种情况下,我不会像您定义的那样评估表达式树,因为这意味着大量的树遍历、间接函数调用等非常昂贵。

    一个俗气的技巧是将实际表达式作为文本字符串吐出,并在其周围带有适当的 C++ 函数正文文本,然后在该文件上启动 C++ 编译器。 您可以使用足够的脚本魔法自动化所有的 spit-compile-relink,这样如果 你很少这样做,这会起作用,你会尽快得到表达式评估 机器可以做到。

    假设您不想这样做,我很想在您开始评估过程之前遍历您的表达式树,并将该树“编译”成一组动作存储在称为“代码”的线性数组中。操作将由枚举定义:

    enum actions {
       // general actions first
       pushx,  // action to push x on a stack
       push1,
       push2,  // action to push 2 on a stack
       ...
       pushN,
       add,
       sub,  
       mul,    // action multiply top two stack elements together
       div,
       ...
       // optimized actions
       add1,
       sub1,
       mul1,
       div1,   // action to divide top stack element by 1
       ...
       addN,
       subN,
       ...
       addx,
       subX,
       ...
     }
    

    在这种情况下,我已经定义了实现下推堆栈表达式求值器的操作,因为这很容易理解。幸运的是,您的表达词汇非常有限,因此您的操作也可能非常有限(如果您有任意变量或常量,它们会更复杂)。

    表达式 ((x*2.0)+x)-1 将由一系列动作执行

     pushx
     mul2
     addx
     sub1
    

    可能很难比这更好。

    可以改为定义操作,以实现遵循多寄存器 CPU 模型的面向寄存器的表达式求值器;这将使执行速度更快(我猜是两倍,但前提是表达式变得非常复杂)。

    您想要的是涵盖您需要执行的最一般计算的操作(因此无论您的原始表达式如何,您始终可以选择一个有效的操作序列)和您遇到的表达式中经常发生的操作(add1 在机器代码,不知道你的统计数据是什么样的,你说你正在做基因编程表明你不知道统计数据是什么,但你可以以某种方式测量它们或做出有根据的猜测)。

    你的内部评估循环看起来像(这里的语法草率):

    float stack[max_depth];
    stack_depth=0;
    for (i=1;i<expression_length;i++)
    {
       switch (code[i])  // one case for each opcode in the enum
       {
        case pushx: stack[stack_depth++]=x;
                   break;
        case push1: stack[stack_depth++]=1;
                   break;
               ...
        case add:   stack[stack_depth-1]+=stack[stack_depth];
                   stack_depth--;
                   break;
               ...
        case subx:  stack[stack_depth]-=x;
                   break;
               ...
        }
    }
    // stack[1] contains the answer here
    

    上面的代码为下推堆栈表达式求值器实现了一个非常快速的“线程解释器”。

    现在“所有”你需要做的就是生成代码数组的内容。你可以这样做 通过使用您的原始表达式树,执行您的原始递归表达式树遍历,而不是执行表达式评估,将您当前的表达式评估器将执行的操作写入代码数组,并在找到它们时吐出特殊情况操作(这相当于“窥孔优化”)。这是经典的树编译,您可以在几乎所有编译器书籍中找到更多关于如何执行此操作的信息。

    是的,这都是一项相当大的工作。但是后来,你决定运行一个遗传算法,这在计算上是相当昂贵的。

    【讨论】:

    • 由于您对这种编译器很陌生,我会坚持我上面建议的“下推堆栈”方案。如果你让它运行,那么也许,也许,你应该考虑寄存器方案。
    【解决方案2】:

    这个帖子中有很多关于加快树迭代的好建议:

    Tree iterator, can you optimize this any further?

    至于这个问题,我猜你可以在不同的线程中处理 Cargo,但你实际上并没有做那么多。您最终可能会在线程同步机制上花费更多时间来完成任何实际工作。

    您可能会发现,如果您只是在进行过程中进行处理,那么您可能会更快地运行,而不是仅仅将其推入双端队列。 Yuo 可能会发现最后在一个单独的循环中处理这一切会更快。找出答案的最佳方法是尝试使用各种不同输入的两种方法并对其计时。

    【讨论】:

    • @Goz:部分问题是如何在递归运行时提取“货物”。我什至不知道该怎么做!链接很有趣。我喜欢你使用连续内存的建议。我有很多要学习的。谢谢
    【解决方案3】:

    假设处理货物的成本足够高,锁定互斥锁相对便宜,您可以在将项目放入队列时使用单独的线程来访问队列。

    线程 1 将执行您当前的逻辑,但它会在添加项目之前锁定队列的互斥锁,然后再解锁。

    然后线程 2 将永远循环,检查队列的大小。如果它不为空,则锁定队列,拉出所有可用的货物并处理它。重复循环。如果没有货物可用,则休眠一小段时间并重复。

    如果锁定成本太高,您可以建立一个队列队列:首先将 100 个项目放入货物队列,然后将该队列放入锁定队列(如第一个示例)。然后从一个新的“本地”队列开始并继续。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-24
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 2021-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多