Skip to content

Commit 07962ab

Browse files
committed
Add benchmark and unit tests to reif.pl
Benchmarks compare performace of memberd/2 with and without goal expansion to memberchk/2. Unit test mostly capture the current behavior, do some sanity checks, couple of corner cases (like handling of cyclic terms) and property test of tfilter/3 and tpartition/4.
1 parent 3522efc commit 07962ab

File tree

8 files changed

+231
-1
lines changed

8 files changed

+231
-1
lines changed

benches/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ cargo bench --bench run_criterion -- --profile-time <time>
2626
2727
# to run iai, you need valgrind installed and to install iai-callgrind-runner
2828
# at the same version as is in Cargo.toml:
29-
cargo install iai-callgrind-runner --version 0.7.3
29+
cargo install iai-callgrind-runner --version 0.9.0
3030
3131
cargo bench --bench run_iai
3232
```

benches/reif.pl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copied from^W^W Inspired by memberbench[1], but has only benchmarks relevant to
3+
goal expansion of if_/3 and is adapted to be better integrated into Scryer's
4+
benchmarking subsystem: which doesn't need time reporting, and supports only
5+
single test at a time.
6+
7+
[1]: http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/sicstus/memberbench.pl
8+
*/
9+
10+
:- use_module(library(reif)).
11+
:- use_module(library(lists)).
12+
:- use_module(library(si)).
13+
14+
15+
run(Test, Count) :-
16+
atom_si(Test),
17+
integer_si(Count),
18+
exptrue(Count),
19+
\+ benchmark(Test, z, "abcdefghijklmnopqrstuvwxyz ")
20+
; true.
21+
22+
23+
% Baseline test – the fastest possible implementation
24+
benchmark(memberchk, E, Es) :- memberchk(E, Es).
25+
26+
% Expanded if_/3
27+
benchmark(memberd_ifc, E, Es) :- memberd_ifc(E, Es).
28+
29+
% Non-expanded if_/3
30+
benchmark(memberd_fif, E, Es) :- memberd_fif(E, Es).
31+
32+
33+
memberd_ifc(X, [E|Es]) :-
34+
if_(X = E, true, memberd_ifc(X, Es)).
35+
36+
memberd_fif(X, [E|Es]) :-
37+
fif_(X = E, true, memberd_fif(X, Es)).
38+
39+
40+
% Copy of _if/3, but with a different name, so it won't be expanded
41+
fif_(If_1, Then_0, Else_0) :-
42+
call(If_1, T),
43+
( T == true -> call(Then_0)
44+
; T == false -> call(Else_0)
45+
; nonvar(T) -> throw(error(type_error(boolean, T), _))
46+
; throw(error(instantiation_error, _))
47+
).
48+
49+
50+
%% exptrue(N).
51+
%
52+
% Succeeds 10^N times if N is ground, usefull as a cheap way to repeat a given
53+
% predicate many times in benchmarks. There are many more ways to generate
54+
% choice points, but this one by far has the lowest overhead.
55+
exptrue(0).
56+
exptrue(1) :- ten.
57+
exptrue(2) :- ten, ten.
58+
exptrue(3) :- ten, ten, ten.
59+
exptrue(4) :- ten, ten, ten, ten.
60+
exptrue(5) :- ten, ten, ten, ten, ten.
61+
exptrue(6) :- ten, ten, ten, ten, ten, ten.
62+
63+
ten. ten. ten. ten. ten. ten. ten. ten. ten. ten.

benches/run_iai.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ mod iai {
1313
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
1414
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
1515
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
16+
#[bench::memberbench_baseline(setup::prolog_benches()["memberbench_baseline"].setup())]
17+
#[bench::memberbench_if_expanded(setup::prolog_benches()["memberbench_if_expanded"].setup())]
18+
#[bench::memberbench_if_not_expanded(setup::prolog_benches()["memberbench_if_not_expanded"].setup())]
1619
fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution {
1720
run()
1821
}

benches/setup.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
2929
Strategy::Reuse,
3030
btreemap! { "Name" => Value::try_from("SPACE".to_string()).unwrap()},
3131
),
32+
(
33+
"memberbench_baseline",
34+
"benches/reif.pl",
35+
"run(memberchk,4).",
36+
Strategy::Reuse,
37+
btreemap! {},
38+
),
39+
(
40+
"memberbench_if_expanded",
41+
"benches/reif.pl",
42+
"run(memberd_ifc,4).",
43+
Strategy::Reuse,
44+
btreemap! {},
45+
),
46+
(
47+
"memberbench_if_not_expanded",
48+
"benches/reif.pl",
49+
"run(memberd_fif,4).",
50+
Strategy::Reuse,
51+
btreemap! {},
52+
),
3253
]
3354
.map(|b| {
3455
(

src/tests/reif.pl

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
:- module(reif_tests, []).
2+
3+
:- use_module(library(reif)).
4+
:- use_module(library(lists)).
5+
:- use_module(library(dif)).
6+
:- use_module(library(loader)).
7+
:- use_module(library(lambda)).
8+
:- use_module(library(random)).
9+
:- use_module(test_framework).
10+
11+
/*
12+
Those tests are just sanity checks – examples from the paper, to make sure I
13+
haven't messed up.
14+
*/
15+
test("indexing dif/2 p6#1", (
16+
findall(X-Fs, tfilter(=(X),[1,2,3,2,3,3],Fs), [1-[1], 2-[2,2], 3-[3,3,3], Y-[]]),
17+
maplist(dif(Y), [1,2,3])
18+
)).
19+
test("indexing dif/2 p6#2", findall(X, duplicate(X,[1,2,3,2,3,3]), [2,3])).
20+
test("indexing dif/2 p7#1", firstduplicate(1, [1,2,3,1])).
21+
test("indexing dif/2 p7#2",(
22+
firstduplicate(X, [1,2,3,1]),
23+
X == 1
24+
)).
25+
test("indexing dif/2 p7#3", (
26+
findall(Y-A-B-C, firstduplicate(Y,[A,B,C]), [X-X-X-_, X-X-B1-X, X-A2-X-X]),
27+
dif(B1,X),
28+
dif(A2,X)
29+
)).
30+
31+
test("doesnt modify free variables", (reif:goal_expanded(A,B), A == B, var(A))).
32+
test("expands call/1", reif:goal_expanded(call(a), a)).
33+
test("expands call/1 for modules", reif:goal_expanded(call(a:b(1)), a:b(1))).
34+
test("expands call/2 for modules", reif:goal_expanded(call(a:b,c), a:b(c))).
35+
test("doesn't expand call/2", reif:goal_expanded(call(b,c), call(b,c))).
36+
test("doesn't expand cyclic terms", (
37+
X=a(X),
38+
reif:goal_expanded(call(X), Y),
39+
call(X) == Y
40+
)).
41+
test("doesn't expand cyclic call/1", (
42+
X=call(X),
43+
reif:goal_expanded(X, Y),
44+
X == Y
45+
)).
46+
test("doesn't expand higher order predicates", (
47+
X = maplist(=(1), _),
48+
reif:goal_expanded(X, Y),
49+
X == Y
50+
)).
51+
52+
/*
53+
Following tests capture current results of goal expansion
54+
TODO: Investigate if if_/3 can be further expanded, and if it will be beneficial
55+
*/
56+
test("goal_expansion (=)", (
57+
subsumes_full_expansion(if_(1=2,a,b), (
58+
1 \= 2 -> b
59+
; 1 == 2 -> a
60+
; 1 = 2, a
61+
; dif(1,2), b)))).
62+
63+
test("goal_expansion (;)", (
64+
subsumes_full_expansion(if_((1=2;3=3),a,b), (
65+
1 \= 2 -> if_(3=3,a,b)
66+
; 1 == 2 -> a
67+
; 1 = 2, a
68+
; dif(1,2), if_(3=3,a,b))))).
69+
70+
test("goal_expansion (,)", (
71+
subsumes_full_expansion(if_((1=2,3=3),a,b), (
72+
1 \= 2 -> b
73+
; 1 == 2 -> if_(3=3,a,b)
74+
; 1 = 2, if_(3=3,a,b)
75+
; dif(1,2), b)))).
76+
77+
test("goal_expansion memberd_t", (
78+
subsumes_full_expansion(if_(memberd_t(f,"abcdefgh"),t,f), (
79+
call(memberd_t(f,"abcdefgh"),A),
80+
( A == true -> t
81+
; A == false -> f
82+
; nonvar(A) -> throw(error(type_error(boolean,A),_))
83+
; throw(error(instantiation_error,_))))))).
84+
85+
test("goal_expansion cond_t", (
86+
subsumes_full_expansion(if_(cond_t(a,b),t,f), (
87+
call(cond_t(a,b),A),
88+
( A == true -> t
89+
; A == false -> f
90+
; nonvar(A) -> throw(error(type_error(boolean,A),_))
91+
; throw(error(instantiation_error,_))))))).
92+
93+
test("set of solutions found by tpartition/4 and tfilter/3 is the same and correct", (
94+
random_test_vector(TestVector),
95+
findall((N,Ts), tpartition(=(N),TestVector,Ts,_), S),
96+
findall((N,Ts), tfilter(=(N),TestVector,Ts), S),
97+
maplist(_+\(N,Ts)^maplist(=(N),Ts), S)
98+
)).
99+
100+
random_test_vector(TestVector) :-
101+
random_integer(0, 1000, Length),
102+
length(TestVector, Length),
103+
maplist(random_integer(1,5), TestVector).
104+
105+
% Expand goal until fix point is found
106+
full_expansion(G, X) :-
107+
user:goal_expansion(G, Gx) -> full_expansion(Gx, X); G = X.
108+
109+
% X is more general than fully expanded goal G
110+
subsumes_full_expansion(G, X) :-
111+
full_expansion(G, Y),
112+
subsumes_term(X, Y).
113+
114+
/*
115+
Extra predicates from the paper
116+
*/
117+
duplicate(X, Xs) :-
118+
tfilter(=(X), Xs, [_,_|_]).
119+
120+
firstduplicate(X, [E|Es]) :-
121+
if_(memberd_t(E,Es), X=E, firstduplicate(X, Es)).
122+
123+
treememberd_t(_, nil, false).
124+
treememberd_t(E, t(F,L,R), T) :-
125+
call((E=F; treememberd_t(E,L); treememberd_t(E,R)), T).
126+
127+
tree_non_member(_, nill).
128+
tree_non_member(E, t(F,L,R)) :-
129+
dif(E,F),
130+
tree_non_member(E, L),
131+
tree_non_member(E, R).

tests/scryer/cli/src_tests/reif_tests.stderr

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
All tests passed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
args = [
2+
"-f",
3+
"--no-add-history",
4+
"-g", "use_module(library(reif_tests))",
5+
"-g", "use_module(library(reif))",
6+
"-g", "use_module(library(dif))",
7+
"-g", "use_module(library(lambda))",
8+
"-g", "use_module(library(lists))",
9+
"-g", "reif_tests:main(reif_tests)",
10+
"src/tests/reif.pl"
11+
]

0 commit comments

Comments
 (0)