【问题标题】:Does Prolog have an alias "operator" like Haskell?Prolog 是否有像 Haskell 这样的别名“运算符”?
【发布时间】:2015-12-24 22:08:40
【问题描述】:

在 Haskell 中,有一个称为“as”-operator(有时称为别名)的语言特性。想法如下:假设您有一个函数,例如将列表作为输入并希望返回所有尾部,您可以将其实现为:

tails a@(_:xs) = a : tails xs
tails [] = [[]]

@ 确保您拥有对整个参数的引用以及对参数结构的某些部分的引用。这是智能性能方面的(它更像是一种性能黑客,因为在第一行的主体中重建数组(x:xs)),如果没有被编译器优化,将导致分配新对象、修改字段等. 更多信息请见here

我想知道 Prolog 是否有类似的东西:例如,如果您想在 Prolog 中实现尾部,可以通过以下方式完成:

tails([H|T],[[H|T]|TA]) :-
    tails(T,TA).
tails([],[[]]).

但如果有像这样的“as”运算符,它可能会更有效:

tails(L@[_|T],[L|TA]) :-  %This does not compile
    tails(T,TA).
tails([],[[]]).

有没有这样的结构,或者语言扩展?

【问题讨论】:

  • 在序言中你可以避免使用别名。只需使用tails(L, [L|TA]) :- L=[_|T], ...
  • 我同意,但如果这在头脑中是可能的,那就太好了。 (我知道我很烦人:S)

标签: performance haskell prolog alias


【解决方案1】:

TL;DR:好主意1!加速似乎被限制在 ~20%(对于大多数列表大小)。

在这个答案中,我们比较了三个不同的谓词,它们在类似@ 的数据重用方面有所不同:

list_tails([], [[]])。 % (1) like `tails/2` 由 OP 给出... list_tails([E|Es], [[E|Es]|Ess]) :- % ....... 但有一个更好的名字 :-) list_tails(Es,Ess)。 list_sfxs1(Es, [Es|Ess]) :- % (2) "重用,相互递归" aux_list_sfxs1(Es,Ess)。 % "sfxs" 是 "suffixes" 的缩写 aux_list_sfxs1([],[])。 aux_list_sfxs1([_|Es], Ess) :- list_sfxs1(Es,Ess)。 list_sfxs2([], [[]])。 %(3)“重用,直接递归” list_sfxs2(Es0, [Es0|Ess]) :- Es0 = [_|Es], list_sfxs2(Es,Ess)。

为了测量运行时间,我们使用以下代码:

:-( dif(D,sicstus), current_prolog_flag(dialect,D) ; use_module(library(between)) )。 run_benchs(P_2s,P_2,L,N,T_ms):- between(1, 6, I), L is 10^I, N 是 10^(8-I), length(Xs, L), member(P_2, P_2s), garbage_collect, call_walltime(run_bench_core(P_2,Xs,N), T_ms)。 run_bench_core(P_2, Xs, N) :- 在(1,N,_)之间, call(P_2, Xs, _), false。 run_bench_core(_, _, _)。

为了测量2,我们使用call_@987654337@/2——call_time/2 的变体:

call_walltime(G, T_ms) :- statistics(walltime, [T0|_]), G, 统计数据(墙上时间,[T1|_]), T_ms 是 T1 - T0。

让我们将上面的代码变体进行测试...

  • ...使用不同的列表长度L...
  • ...并多次运行每个测试N(以获得更好的准确性)。

首先,我们使用 7.3.14 版(64 位):

?- run_benchs([list_sfxs1,list_sfxs2,list_tails], P_2, L, N, T_ms)。 P_2 = list_sfxs1,L*N = 10*10000000,T_ms = 7925 ; P_2 = list_sfxs2,L*N = 10*10000000,T_ms = 7524 ; P_2 = list_tails,L*N = 10*10000000,T_ms = 6936 ; P_2 = list_sfxs1,L*N = 100*1000000,T_ms = 6502 ; P_2 = list_sfxs2,L*N = 100*1000000,T_ms = 5861 ; P_2 = list_tails,L*N = 100*1000000,T_ms = 5618 ; P_2 = list_sfxs1,L*N = 1000*100000,T_ms = 6434 ; P_2 = list_sfxs2,L*N = 1000*100000,T_ms = 5817 ; P_2 = list_tails,L*N = 1000*100000,T_ms = 9916 ; P_2 = list_sfxs1,L*N = 10000*10000,T_ms = 6328 ; P_2 = list_sfxs2,L*N = 10000*10000,T_ms = 5688 ; P_2 = list_tails,L*N = 10000*10000,T_ms = 9442 ; P_2 = list_sfxs1,L*N = 100000*1000,T_ms = 10255 ; P_2 = list_sfxs2,L*N = 100000*1000,T_ms = 10296 ; P_2 = list_tails,L*N = 100000*1000,T_ms = 14592 ; P_2 = list_sfxs1,L*N = 1000000*100,T_ms = 6955 ; P_2 = list_sfxs2,L*N = 1000000*100,T_ms = 6534 ; P_2 = list_tails,L*N = 1000000*100,T_ms = 9738。

然后,我们使用 4.3.2 版(64 位)重复上一个查询3

?- run_benchs([list_sfxs1,list_sfxs2,list_tails], P_2, L, N, T_ms)。 P_2 = list_sfxs1,L*N = 10*10000000,T_ms = 1580 ; P_2 = list_sfxs2,L*N = 10*10000000,T_ms = 1610 ; P_2 = list_tails,L*N = 10*10000000,T_ms = 1580 ; P_2 = list_sfxs1,L*N = 100*1000000,T_ms = 710 ; P_2 = list_sfxs2,L*N = 100*1000000,T_ms = 750 ; P_2 = list_tails,L*N = 100*1000000,T_ms = 840 ; P_2 = list_sfxs1,L*N = 1000*100000,T_ms = 650 ; P_2 = list_sfxs2,L*N = 1000*100000,T_ms = 660 ; P_2 = list_tails,L*N = 1000*100000,T_ms = 740 ; P_2 = list_sfxs1,L*N = 10000*10000,T_ms = 620 ; P_2 = list_sfxs2,L*N = 10000*10000,T_ms = 650 ; P_2 = list_tails,L*N = 10000*10000,T_ms = 740 ; P_2 = list_sfxs1,L*N = 100000*1000,T_ms = 670 ; P_2 = list_sfxs2,L*N = 100000*1000,T_ms = 650 ; P_2 = list_tails,L*N = 100000*1000,T_ms = 750 ; P_2 = list_sfxs1,L*N = 1000000*100,T_ms = 12610 ; P_2 = list_sfxs2,L*N = 1000000*100,T_ms = 12560 ; P_2 = list_tails,L*N = 1000000*100,T_ms = 33460。

总结:

  • alias-thingy 可以并且确实提高了性能
  • 在上述测试中,与 SWI-Prolog 相比,SICStus Prolog 4 提供 10 倍加速

脚注 1: 为什么将(@)/2 放在规则头中? 以非idiomatic Prolog 代码结束?
脚注 2: 我们对总运行时间感兴趣。为什么?因为垃圾收集成本随着数据量的增大而显现!
脚注 3:为了便于阅读,已对答案序列进行了后处理。
脚注 4:release 4.3.0 起可用。当前的目标架构包括IA-32AMD64

【讨论】:

  • 我想知道是否将这样的东西放在头脑中不会导致额外的优化。对于tails,它并不是很有用,但现在您推迟了在调用该谓词之前可能已经完成的检查。尽管如此,令人印象深刻的答案 +1。
  • 此外,我想知道是否无法改进编译器以发现 [E|Es] 被使用了两次,从而自己构造了一个“隐式”别名。
  • @WillemVanOnsem。是的,原则上,Prolog 编译器可以做到这一点。 OTOH 采用具有惰性求值的纯函数式语言(如 Haskell),这更加直接。 Prolog 编译很棘手,如果您想确保编译后的代码和模拟解释的代码永远永远表现不同。许多微妙之处/细节需要正确处理。 SICStus JIT 相对较新......而且非常令人印象深刻!
  • @WillemVanOnsem。哦,顺便说一句:优化可以很有趣,也是很好的学习体验,但它不应该是的主要焦点!考虑:prolog-heritage.org/en/ph4.html .
猜你喜欢
  • 1970-01-01
  • 2011-10-26
  • 2011-05-04
  • 2017-05-17
  • 2017-07-24
  • 2016-12-21
  • 2010-10-05
相关资源
最近更新 更多