Skip to content

Commit 9ac7595

Browse files
committed
Some performance improvements
The map_no_copy and instruction visiting functions in the visitor implementation were not tail recursive. This commit adds tail recursive versions of those functions. This improves both memory use and performance.
1 parent c7a1bcc commit 9ac7595

3 files changed

Lines changed: 58 additions & 25 deletions

File tree

src/lib/jib_visitor.ml

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,45 @@ let rec visit_instr vis outer_instr =
240240
do_visit vis (vis#vinstr outer_instr) aux outer_instr
241241

242242
and visit_instrs vis outer_instrs =
243-
let aux vis no_change =
244-
match no_change with
243+
let rec commit rev shared n =
244+
if n = 0 then (rev, shared) else (match shared with x :: t -> commit (x :: rev) t (n - 1) | [] -> (rev, shared))
245+
in
246+
let rec unwind result = function
247+
| [] -> result
248+
| (rev_prefix, f) :: posts -> unwind (List.rev_append rev_prefix (f result)) posts
249+
in
250+
let rec go rev shared pending posts instrs =
251+
match vis#vinstrs instrs with
252+
| SkipChildren -> unwind (List.rev_append rev shared) posts
253+
| ChangeTo instrs' ->
254+
let rev, _ = commit rev shared pending in
255+
unwind (List.rev_append rev instrs') posts
256+
| DoChildren -> children rev shared pending posts instrs
257+
| DoChildrenPost f ->
258+
let rev, _ = commit rev shared pending in
259+
children [] instrs 0
260+
(( rev,
261+
fun r ->
262+
f ();
263+
r
264+
)
265+
:: posts
266+
)
267+
instrs
268+
| ChangeDoChildrenPost (instrs', f) ->
269+
let rev, _ = commit rev shared pending in
270+
children [] instrs' 0 ((rev, f) :: posts) instrs'
271+
and children rev shared pending posts = function
272+
| [] -> unwind (List.rev_append rev shared) posts
245273
| instr :: instrs ->
246274
let instr' = visit_instr vis instr in
247-
let instrs' = visit_instrs vis instrs in
248-
if instr == instr' && instrs == instrs' then no_change else instr' :: instrs'
249-
| [] -> []
275+
if instr' != instr then (
276+
let rev, _ = commit rev shared pending in
277+
go (instr' :: rev) instrs 0 posts instrs
278+
)
279+
else go rev shared (pending + 1) posts instrs
250280
in
251-
do_visit vis (vis#vinstrs outer_instrs) aux outer_instrs
281+
go [] outer_instrs 0 [] outer_instrs
252282

253283
and visit_ctype_def vis no_change =
254284
match no_change with

src/lib/type_check.ml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,11 +742,10 @@ let unify l env goals typ1 typ2 =
742742
)
743743
else unify_typ l env goals typ1 typ2
744744

745-
let subst_unifiers unifiers typ =
746-
List.fold_left (fun typ (v, arg) -> typ_subst v arg typ) typ (KBindings.bindings unifiers)
745+
let subst_unifiers unifiers typ = KBindings.fold (fun v arg typ -> typ_subst v arg typ) unifiers typ
747746

748747
let subst_unifiers_typ_arg unifiers typ_arg =
749-
List.fold_left (fun typ_arg (v, arg) -> typ_arg_subst v arg typ_arg) typ_arg (KBindings.bindings unifiers)
748+
KBindings.fold (fun v arg typ_arg -> typ_arg_subst v arg typ_arg) unifiers typ_arg
750749

751750
let instantiate_quant (v, arg) (QI_aux (aux, l) as qi) =
752751
match aux with

src/lib/visitor.ml

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,26 @@ let do_visit (vis : 'v) (action : 'a visit_action) (children : 'v -> 'a -> 'a) (
125125

126126
let change_do_children node' = ChangeDoChildrenPost (node', fun n -> n)
127127

128-
(* map_no_copy is like map but avoid copying the list if the function does not
129-
* change the elements. *)
130-
let rec map_no_copy (f : 'a -> 'a) = function
131-
| [] -> []
132-
| i :: resti as li ->
133-
let i' = f i in
134-
let resti' = map_no_copy f resti in
135-
if i' != i || resti' != resti then i' :: resti' else li
136-
137-
let rec map_no_copy_list (f : 'a -> 'a list) = function
138-
| [] -> []
139-
| i :: resti as li -> (
140-
let il' = f i in
141-
let resti' = map_no_copy_list f resti in
142-
match il' with [i'] when i' == i && resti' == resti -> li | _ -> il' @ resti'
143-
)
128+
(* map_no_copy is like map but avoids copying the list if the function does not
129+
change any element (compared with physical equality). It is tail recursive:
130+
elements up to the last change are rebuilt while the unchanged suffix is
131+
shared with the original list, so nothing is allocated when nothing changes.
132+
f is applied left-to-right, once per element. *)
133+
let map_no_copy (f : 'a -> 'a) li =
134+
let rec commit rev shared n =
135+
if n = 0 then (rev, shared) else (match shared with x :: t -> commit (x :: rev) t (n - 1) | [] -> (rev, shared))
136+
in
137+
let rec go rev shared pending = function
138+
| [] -> List.rev_append rev shared
139+
| i :: resti ->
140+
let i' = f i in
141+
if i' != i then (
142+
let rev, shared = commit rev shared pending in
143+
go (i' :: rev) resti 0 resti
144+
)
145+
else go rev shared (pending + 1) resti
146+
in
147+
go [] li 0 li
144148

145149
(* not part of original cil framework *)
146150
let map_no_copy_opt (f : 'a -> 'a) : 'a option -> 'a option = function

0 commit comments

Comments
 (0)