【问题标题】:Where might I begin on this optimization problem?我可以从哪里开始解决这个优化问题?
【发布时间】:2010-08-02 15:56:30
【问题描述】:

我有一个简单的程序,它从文件系统中读取一堆东西,过滤结果,然后打印出来。这个简单的程序实现了一种特定领域的语言,使选择更容易。这个 DSL “编译”成如下所示的执行计划(输入为 C:\Windows\System32\* OR -md5"ABCDEFG" OR -tf):

Index  Success  Failure  Description
    0        S        1  File Matches C:\Windows\System32\*
    1        S        2  File MD5 Matches ABCDEFG
    2        S        F  File is file. (Not directory)

过滤器应用于给定文件,如果成功,则索引指针跳转到成功字段中指示的索引,如果失败,则索引指针跳转到失败字段中指示的数字。 “S”表示文件通过过滤器,F表示文件应该被拒绝。

当然,基于简单文件属性 (!FILE_ATTRIBUTE_DIRECTORY) 检查的过滤器比基于文件 MD5 的检查要快得多,后者需要打开并执行文件的实际哈希。每个过滤器“操作码”都有一个与之关联的时间类; MD5 获得高计时号,ISFILE 获得低计时号。

我想重新排序这个执行计划,以便尽可能少地执行需要很长时间的操作码。对于上述计划,这意味着它必须是:

Index  Success  Failure  Description
    0        S        1  File is file. (Not directory)
    1        S        2  File Matches C:\Windows\System32\*
    2        S        F  File MD5 Matches ABCDEFG

根据“龙书”,为三个地址代码选择最佳执行顺序是一个 NP-Complete 问题(至少根据该文本第二版的第 511 页),但在那种情况下,他们正在谈论关于寄存器分配和机器的其他问题。就我而言,实际的“中间代码”要简单得多。我想知道是否存在允许我将源 IL 重新排序为最佳执行计划的方案。

这是另一个例子:
{ C:\Windows\Inf* AND -tp } OR { -tf AND NOT C:\Windows\System32\Drivers* }

解析为:

Index  Success  Failure  Description
    0        1        2  File Matches C:\Windows\Inf\*
    1        S        2  File is a Portable Executable
    2        3        F  File is file. (Not directory)
    3        F        S  File Matches C:\Windows\System32\Drivers\*

这是最优的:

Index  Success  Failure  Description
    0        1        2  File is file. (Not directory)
    1        2        S  File Matches C:\Windows\System32\Drivers\*
    2        3        F  File Matches C:\Windows\Inf\*
    3        S        F  File is a Portable Executable

【问题讨论】:

  • 我认为这里没有太大问题。为什么不能按照“成本”启发式严格排序?
  • @Neil:我想这与 C++ 无关。移除标签。 @strager:呃……如果你只是排序,那么你会改变表达式的含义。
  • 啊,我误会了。 @Greg Hewgill 的图表帮助我理解了。

标签: language-agnostic optimization compiler-construction query-optimization


【解决方案1】:

听起来在编译成您的操作码之前选择最佳顺序可能更容易。如果您有一个解析树,并且它尽可能“平坦”,那么您可以为每个节点分配一个分数,然后首先按最低总分对每个节点的子节点进行排序。

例如:

{ C:\Windows\Inf* AND -tp } OR { -tf AND NOT C:\Windows\System32\Drivers* }
         1             2          3                 4

      OR
     /  \
  AND    AND
 /  \   /   \
1    2 3     4

您可以按最低分数对 AND 节点 (1, 2) 和 (3, 4) 进行排序,然后将该分数分配给每个节点。然后按照 他们的个孩子的最低分数对 OR 节点的孩子进行排序。

由于 AND 和 OR 是可交换的,所以这个排序操作不会改变你整体表达的意思。

【讨论】:

  • 这是个好主意,但它不起作用,比如 { -tf OR C:\Blach OR -tp } OR { C:\Windows* OR -tp },因为括号子表达式在 OR 下,最佳情况是在左侧 -tp 之前评估 C:\Windows*。
  • 这种情况实际上已被我神秘的“尽可能平坦”的评论所涵盖。由于 OR 都是可交换的,因此您的分组不相关,所有这些 OR 条件都应该一起考虑。您可以按您喜欢的任何顺序对它们进行排序。您可能必须对原始解析树应用转换,才能像这样展平条件组。
  • @Greg:啊,我明白了。仍然宁愿寻找一种适用于“已编译”代码的解决方案,因为我不想重写整个解析器以使用抽象语法树(它在没有实际使用树的情况下生成它),但现在 +1 .
  • @Billy ONeal,之后构建树然后重新展平它有什么问题(除了有些多余)?
  • @strager: Err.. 你将如何构建这样一棵树? IL 不会完全反编译回源表达式。
【解决方案2】:

@Greg Hewgill 是对的,这在 AST 上比在中间代码上更容易执行。当您想要处理中间代码时,第一个目标是将其转换为依赖树(看起来像 AST /shrug)。

从叶子开始——如果你对 NOT 使用否定谓词,这可能是最简单的。

Index  Success  Failure  Description
0        1        2  File Matches C:\Windows\Inf\*
1        S        2  File is a Portable Executable
2        3        F  File is file. (Not directory)
3        F        S  File Matches C:\Windows\System32\Drivers\*

提取叶子(任何同时具有 S、F 或提取节点的子节点;在需要的地方插入 NOT;将所有对叶子的引用替换为对叶子的父节点的引用)

Index  Success  Failure  Description
0        1        2  File Matches C:\Windows\Inf\*
1        S        2  File is a Portable Executable
2        L1        F  File is file. (Not directory)

L1=NOT(cost(child))
    |
Pred(cost(PATH))

提取节点(如果成功指向提取节点使用连词连接;失败使用析取;将所有对节点的引用替换为对包含节点的树根的引用)。

Index  Success  Failure  Description
0        1        L3  File Matches C:\Windows\Inf\*
1        S        L3  File is a Portable Executable


L3=AND L1 L2 (cost(Min(L1,L2) + Selectivity(Min(L1,L2)) * Max(L1,L2)))
               /           \
L1=NOT(cost(child))     L2=IS(cost(child))
    |                       |
3=Pred(cost(PATH))      2=Pred(cost(ISFILE))

提取节点

Index  Success  Failure  Description
0        L5       L3  File Matches C:\Windows\Inf\*

L5=OR L3 L4 (cost(Min(L3,L4) + (1.0 - Selectivity(Min(L3,L4))) * Max(L3,L4)))
                    /                          \
                    |                       L4=IS(cost(child))
                    |                           | 
                    |                       1=Pred(cost(PORT_EXE))
                    |
L3=AND L1 L2 (cost(Min(L1,L2) + Selectivity(Min(L1,L2)) * Max(L1,L2)))
               /           \
L1=NOT(cost(child))     L2=IS(cost(child))
    |                       |
3=Pred(cost(PATH))      2=Pred(cost(ISFILE))

提取节点(如果成功和失败都指节点,则必须在节点定义的子树的根上通过模式匹配将节点注入树中)

  1. 如果 root 为 OR,则在必要时反转谓词以确保引用为 Success,并与未被 Failure 引用的子项结合注入。

  2. 如果根为 AND,则在必要时反转谓词以确保引用为失败并作为与 Success 引用的子根的析取注入。

导致:

L5=OR L3 L4 (cost(Min(L3,L4) + (1.0 - Selectivity(Min(L3,L4))) * Max(L3,L4)))
                    /                          \
                    |                       L4=AND(cost(as for L3))
                    |                             /               \
                    |                       L6=IS(cost(child))   L7=IS(cost(child))
                    |                           |                       |
                    |                       1=Pred(cost(PORT_EXE))   0=Pred(cost(PATH))
                    |
L3=AND L1 L2 (cost(Min(L1,L2) + Selectivity(Min(L1,L2)) * Max(L1,L2)))
               /           \
L1=NOT(cost(child))     L2=IS(cost(child))
    |                       |
3=Pred(cost(PATH))      2=Pred(cost(ISFILE))

【讨论】:

  • "这看起来像 AST /shrug"
  • 我担心我提出的算法是基于模式匹配的。如果您正在使用 F#、ML、Scala、Haskell 或具有良好模式匹配库的东西(我已经看到一个可用于方案的好的库),那么用命令式 OOP 语言(C#、Java、C++ 等)实现它是会长毛的。之前以功能模式匹配和命令式 oop 形式实现了这两种方法,如果可以选择,我更愿意编写应用基本布尔标识的树遍历重写器。
  • 这里涉及到模式匹配哪里? (IL 本身实际上并没有以这种基于字符串的格式存储——它作为一组“OpCode”对象存储在一个向量中)这具有额外的优势,即优化器可以是 A. 打开和关闭,以及 B . 如果/当解析器发生变化(即适应新的语言特性)时,不需要修改
猜你喜欢
  • 2011-05-09
  • 2013-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
相关资源
最近更新 更多