2

在玩 Logtalk 时,似乎我的程序用 Logtalk 对象执行比普通 Prolog 执行的时间更长。我做了一个基准测试,比较了普通 Prolog 中简单谓词的执行与下面等效的 logtalk 对象封装:

%%
% plain prolog predicate
plain_prolog_simple :-
  fail.

%%
% object encapsulation
:- object(logtalk_obj).

    :- public([simple/0]).
    simple :-
      fail.

:- end_object. 

这是我得到的:

?- benchmark(plain_prolog_simple).

Number of repetitions: 500000
Total time calls: 0.33799099922180176 seconds
Average time per call: 6.759819984436035e-7 seconds
Number of calls per second: 1479329.3346604244
true.

?- benchmark(logtalk_obj::simple).

Number of repetitions: 500000
Total time calls: 2.950408935546875 seconds
Average time per call: 5.90081787109375e-6 seconds
Number of calls per second: 169468.0333888435
true.

我们可以看到logtalk_obj::simple callplain_prolog_simple调用慢。我使用 SWI Prolog 作为后端,我尝试设置一些日志对话标志,但没有成功。

编辑:我们可以在https://github.com/koryonik/logtalk-experiments/tree/master/benchmarks找到基准代码示例

怎么了 ?为什么会有这种性能差异?如何优化 Logtalk 方法调用?

4

3 回答 3

4

简而言之,您是在顶级 INTERPRETER::/2对目标的 Logtalk 编译进行基准测试。这是一个典型的基准测试错误。顶层的目标,无论是普通的 Prolog 目标、模块明确限定的谓词目标还是消息发送目标,总是会被解释,即动态编译。

在编译的源文件中,您可以获得接近普通 Prolog 的性能,这是最常见的场景。benchmarks有关避免上述陷阱的基准测试解决方案,请参阅 Logtalk 分发中的示例。

性能差距(普通 Prolog 和 Logtalk 目标之间)取决于所选的后端 Prolog 编译器。当可以进行静态绑定时,成熟的 Prolog VM(例如 SICStus Prolog 或 ECLiPSe)的差距可以忽略不计。然而,一些 Prolog VM(例如 SWI-Prolog)缺乏一些可以使差距变大的优化,特别是在紧密循环中。

PS Logtalk 开箱即用,带有用于开发的设置配置,而不是用于性能。请特别参阅标志上的文档,该optimize标志应为静态绑定优化打开。

更新

从存储库中的代码开始,并假设 SWI-Prolog 作为后端编译器,尝试:

----- code.lgt -----
% plain prolog predicate
plain_prolog_simple :-
  fail.

% object encapsulation
:- object(logtalk_obj).

    :- public(simple/0).
    simple :-
      fail.

:- end_object.
--------------------

----- bench.lgt -----
% load the SWI-Prolog "statistics" library
:- use_module(library(statistics)).   

:- object(bench).

    :- public(bench/0).
    bench :-
        write('Plain Prolog goal:'), nl,
        prolog_statistics:time({plain_prolog_simple}).
    bench :-
        write('Logtalk goal:'), nl,
        prolog_statistics:time(logtalk_obj::simple).
    bench.

:- end_object.
---------------------

保存这两个文件,然后启动 Logtalk:

$ swilgt
...
?- set_logtalk_flag(optimize, on).
true.

?- {code, bench}.
% [ /Users/pmoura/Desktop/bench/code.lgt loaded ]
% (0 warnings)
% [ /Users/pmoura/Desktop/bench/bench.lgt loaded ]
% (0 warnings)
true.

?- bench::bench.
Plain Prolog goal:
% 2 inferences, 0.000 CPU in 0.000 seconds (69% CPU, 125000 Lips)
Logtalk goal:
% 2 inferences, 0.000 CPU in 0.000 seconds (70% CPU, 285714 Lips)
true.

time/1谓词是元谓词。Logtalk 编译器使用元谓词属性来编译time/1参数。{}/1控制结构是 Logtalk 编译器绕过。它确保其参数在普通 Prolog 数据库中按原样调用。

于 2015-09-26T17:22:39.707 回答
1

与提供time/1元谓词的 SWI-Prolog 和 YAP(可能还有其他)一起使用的基准测试技巧是将此谓词与 Logtalk 的<</2调试控制结构和logtalk内置对象一起使用。使用 SWI-Prolog 作为后端编译器:

?- set_logtalk_flag(optimize, on).
...
?- time(true).  % ensure the library providing time/1 is loaded
...
?- {code}.
...
?- time(plain_prolog_simple).
% 2 inferences, 0.000 CPU in 0.000 seconds (59% CPU, 153846 Lips)
false.
?- logtalk<<(prolog_statistics:time(logtalk_obj::simple)).
% 2 inferences, 0.000 CPU in 0.000 seconds (47% CPU, 250000 Lips)
false.

快速解释一下,<</2控件构造在调用它之前编译它的目标参数。由于optimize标志被打开并且time/1是一个元谓词,它的参数是完全编译的,并且静态绑定用于消息发送。因此,我们在上面得到的推论数量相同。因此,此技巧允许您在顶层为 Logtalk 消息发送目标进行快速基准测试。

time/1使用 YAP 与使用内置元谓词类似但更简单,而不是像 SWI-Prolog 中的库元谓词。

于 2016-05-20T11:01:05.230 回答
0

您还可以为面向对象制作非常快的解释器。Jekejeke Prolog 有一个纯解释的 (::)/2 运算符。目前没有太多开销。这是测试代码:

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- [user].

plain :- fail.

:- begin_module(obj).

simple(_) :- fail.

:- end_module.

这些是一些实际的结果。普通呼叫和基于 (::)/2 运营商的呼叫之间没有那么大的区别。在后台,两个谓词查找都是内联缓存的:

?- time((between(1,500000,_), plain, fail; true)).
% Up 76 ms, GC 0 ms, Thread Cpu 78 ms (Current 06/23/18 23:02:41)
Yes

?- time((between(1,500000,_), obj::simple, fail; true)).
% Up 142 ms, GC 11 ms, Thread Cpu 125 ms (Current 06/23/18 23:02:44)
Yes

我们还有一个开销,将来可能会被删除。必须这样做,我们仍然为每个 (::)/2 调用进行微型重写。但也许这会消失,我们正在努力。

编辑 23.06.2018:我们现在有一个内置的 between/3 并且已经实现了一些优化。上图展示了这个尚未发布的新原型的预览。

于 2018-06-09T13:19:17.713 回答