【问题标题】:Performance of the built-in Prolog predicate (is)/2内置 Prolog 谓词 (is)/2 的性能
【发布时间】:2016-05-11 19:05:10
【问题描述】:

更新:11.6.2016

我在 SICStus Prolog 4.3.2 中观察到的令人费解的性能差异在最近发布的 SICStus Prolog 4.3.3 中完全消失了荣誉!

我更新了下面的“运行时”表以包括 SICStus Prolog 4.3.3。亮点包括:

  • (is)/2 比以前快了 10 倍
  • val_of/2 的速度也非常快,几乎提高了 2 倍

MEGO;-)


answering 问题“Size Procedure in Prolog Language SO-user @ThanosTintinidis 提出了一个非常简单的方法1介绍初学者导出length/2

[...] 另请注意,E 仅需要实例化,因为 is 将评估表达式。你可以这样写:

大小([],0)。
尺寸([_|Xs], 1+E) :- 尺寸(Xs, E)。

如果你这么称呼它:

?- size([_,_], E)。
E = 1+(1+0)。

很有趣,不是吗?您可能想要评估最后一个E,即调用?- size([1,2], E), N is E。 [...]

有趣吗? 非常有趣! 许多有趣的实验摆在面前:

  • 左倾与右倾树

    list_sizL([], 0)。 % l左倾 list_sizL([_|Es], N+1) :- list_sizL(Es,N)。 list_sizR([], 0)。 % r右倾 list_sizR([_|Es], 1+N) :- list_sizR(Es,N)。
  • 内置(is)/2 vs val_of/2

    val_of(V, E) :- ( E = E1+E2 -> val_of(V1, E1), val_of(V2, E2), V 是 V1+V2 ;数字(E)-> V = E )。

为了测量运行时间,我使用不同的 Prolog 处理器运行了 go(2000000)2

去(左):- 长度(Xs,L), 成员(B_2,[list_sizL,list_sizR]), 呼叫(B_2,Xs,E), 成员(P_2,[是,val_of]), call_time(呼叫(P_2,N,E), T), ( L = N -> writeq(B_2+P_2=T), nl ; throw(up) )。

Intel Core i7-4700MQ 上,我使用 SICStus 和 SWI 观察到以下运行时:

| SWI | SICStus | SICStus | | 7.3.20 | 4.3.2 | 4.3.3 | -------------------+--------+---------+---------| list_sizL + (is) | 208 毫秒 | 650 毫秒 | 60 毫秒 | 3.4 倍 list_sizL + val_of | 381 毫秒 | 100 毫秒 | 60 毫秒 | 6.3 倍 -------------------+--------+---------+---------| list_sizR + (is) | 88 毫秒 | 660 毫秒 | 70 毫秒 | 1.2x list_sizR + val_of | 346 毫秒 | 100 毫秒 | 60 毫秒 | 5.7 倍 -------------------+--------+---------+---------|

我对这些(可重现的)结果感到困惑......有人可以告诉我发生了什么吗?


脚注 1:为了简洁和可读性,变量名称稍作修改。
脚注 2:运行 SWI-Prolog 7.3.20带有合适的命令行参数swipl -G2G -L2G -O

【问题讨论】:

    标签: prolog swi-prolog sicstus-prolog


    【解决方案1】:

    我可以确认一个令人惊讶的事实,即在 SICStus Prolog 中,当表达式是一个大的复合词时,val_of/2is/2 快得多,即当 is/2 解释一个表达式时。

    在当前的 SICStus 实现中,当算术运算符的参数不是数字时,is/2 需要转义到 Prolog 代码。在解释深层表达式时,所有参数都会以递归方式进行转义,这比 val_of/2 所做的要慢得多,即从普通的 Prolog 到 Prolog 调用。

    我对潜在问题进行了概念验证修复,它使上述程序中的is/2 案例与val_of/2 的速度大致相同。此更改已包含在当前版本的 SICStus Prolog (4.3.3) 中。

    【讨论】:

    • .. 采用 acyclic_term/1ground/1 来支持 TRO 怎么样?至少在 SWI 中,这会产生 2-3 的加速。
    • 完美!我相应地更新了我的问题中的基准表...再次致敬!
    【解决方案2】:

    您正在比较(is)/2 的两个非常不同的实现。 SWI 在实际评估之前检测实例化错误和循环表达式。 SICStus 没有。试试这个:

    ?- E=(1/0)+_, X is E.
    ?- E=(1/0)+E, X is E.
    

    SWI 产生一个实例化错误和一个类型错误(因为无限树),而 SICStus 总是评估 1/0 并在这两种情况下产生一个评估错误。而且(至少对于这个例子来说),两者都符合要求。

    评估两个树结构的速度差异是由于 SWI 中 ground/1acyclic_term/1 中的尾递归优化。当前算法使用堆栈并从左到右访问参数。因此,嵌套在右侧的树需要恒定的辅助空间,因此速度要快得多。

    SICStus 将 Deutsch-Schorr-Waite 用于 acyclic_term/1ground/1,因此没有显式堆栈,因此没有 TRO。至少,左右两边的速度差不多。

    【讨论】:

    • 有趣!我仍然无法绕过它。 Deutsch-Schorr-Waite 的主要优点是用于树遍历的内存使用率较低,对吧?那么,为什么 SICStus (is)/2(本质上)用 L is 10^7, go(L) 耗尽内存?这对我来说没有意义。
    • @repeat:SICStus 不使用 DSW 进行表达式评估。在此示例中,它既不使用 ground/1 也不使用 acyclic_term/1。所以其他东西是造成溢出的原因。
    • 感谢 Deutsch-Schorr-Waite。 awesome
    【解决方案3】:

    简单地说,我认为 SWI-Prolog 对算术进行了更多优化,而 SICStus 对一般控制流有更好的代码生成。

    也许可以通过 Partial Evaluation 从 SWI-Prolog 获得更好的性能,Pereira-Shieber 在 Prolog and Natural-Language Analysis 的第 6 章中很好地介绍了这一点。

    您还应该给 YAP 一个机会:据报道,它是最快的基于 WAM 的 Prolog。我记得 Jan Wielemaker 在 SWI-Prolog 列表中指出,大部分 YAP 速度是通过大量重写获得的——比如说内联。我认为它建立在(当然实施得很好)部分评估的基础上。

    【讨论】:

    • yap 64 位 Linux Mint 17 附带的版本 6.2.2(“稳定”)立即崩溃。当前版本可能更好,但我还没有尝试过任何更新的东西(还)......
    • YAP 变得非常难以安装。至少我无法安装它,因为它使用cmake
    • Linux Mint 17 中的 cmake 包不起作用——太旧了。使用 yap-devel 获取和编译 cmake.org/files/v3.5/cmake-3.5.2.tar.gz OTOH 确实有效
    • 尖端技术
    猜你喜欢
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多