在使用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 call
比plain_prolog_simple
调用慢。
我使用SWI Prolog作为后端,我尝试设置一些日志通话标志,但没有成功。
编辑:我们可以找到基准代码示例到https://github.com/koryonik/logtalk-experiments/tree/master/benchmarks
出了什么问题?为什么这种性能差异?如何优化Logtalk方法调用?
答案 0 :(得分:3)
简而言之,您在顶级INTERPRETER 中对::/2
目标的Logtalk编译进行了基准测试。这是一个经典的基准测试错误。在顶层的目标,无论是普通的Prolog目标,模块明确限定的谓词目标,还是消息发送目标,都将被解释,即即时编译。
对于已编译源文件中的邮件发送目标,您可以获得与普通Prolog相近的性能,这是最常见的情况。请参阅Logtalk发行版中的benchmarks
示例,以获取避免上述陷阱的基准测试解决方案。
性能差距(普通Prolog和Logtalk目标之间)取决于所选择的后端Prolog编译器。当可以进行静态绑定时,成熟Prolog VM(例如SICStus Prolog或ECLiPSe)的差距可以忽略不计。一些Prolog虚拟机(例如SWI-Prolog)缺乏一些优化,可以使差距更大,特别是在紧密循环中。
P.S。 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数据库中按原样调用其参数。
答案 1 :(得分:1)
与提供time/1
元谓词的SWI-Prolog和YAP(可能是其他)一起使用的基准测试技巧是将此谓词与Logtalk的<</2
调试控制结构和{{1}一起使用内置对象。使用SWI-Prolog作为后端编译器:
logtalk
快速解释,?- 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
是元谓词,因此其参数已完全编译,静态绑定用于发送消息。因此,我们得到的推论数量相同。因此,这个技巧允许您在Logtalk消息发送目标的顶层进行快速基准测试。
使用YAP类似但更简单,因为time/1
是内置的元谓词,而不是SWI-Prolog中的库元谓词。
答案 2 :(得分:1)
您还可以为面向对象制作非常快速的解释器。 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:我们现在已经在/ 3之间内置并且已经实现了一些优化。上面的数字显示了这个尚未推出的新原型的预览。