Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3bbf3eb
Add a LRU cache to limit indexing memory usage.
Lucccyo Mar 31, 2026
f20f772
Add changelog for #2079
voodoos Jun 15, 2026
5ae38c7
Promote test change
voodoos Jun 15, 2026
70af090
refmt
voodoos Jun 15, 2026
88680e9
On_disk_small: Reuse store and loc from parents
voodoos Jun 24, 2026
8873a25
Promote parent in LRU when fetching a child
voodoos Jun 24, 2026
6f1f871
Rename fetch_parent
voodoos Jun 24, 2026
418a890
Make schema optional in `On_disk` to remove placeholder usages of `On…
voodoos Jun 24, 2026
ba73179
Factorize merge_union function
voodoos Jun 24, 2026
ebd1f00
Always upgrade reused links schemas
voodoos Jun 24, 2026
a07ff2a
Serialized should use the same cache logic as Serialized_reused
voodoos Jun 24, 2026
3b94bad
Store info directly in `In_cache` instead of the LRU cell
voodoos Jun 24, 2026
5cbfbd0
Use kb for chache size flag, default to 1gb
voodoos Jun 30, 2026
d8c9331
Remove todo
voodoos Jul 3, 2026
bc9c08e
Document default cache size
voodoos Jul 3, 2026
ba39e1e
find_and_wompress: directly return the root
voodoos Jul 3, 2026
daf5b2a
Union_find.union, no need for a new link
voodoos Jul 3, 2026
50cd87a
Make the union find store more granular.
voodoos Jul 3, 2026
69afef5
Add missing cache use
voodoos Jul 3, 2026
d9e1c02
Factor out the cache logic
voodoos Jul 3, 2026
b80cfc7
Also skip smalls that have already been skipped
voodoos Jul 3, 2026
5a288d9
Remove unused Serialized_reused
voodoos Jul 3, 2026
7b8bf4c
Remove remaining occurrence of Serialized_reused
voodoos Jul 7, 2026
8302914
Split On_disk_ptr in two to better distinguish smalls
voodoos Jul 7, 2026
50c0ad2
Push missing fix
voodoos Jul 9, 2026
f5205a0
Fix Union_find.merge in the general case
voodoos Jul 9, 2026
844955d
In_cache: remove redundant status field
voodoos Jul 9, 2026
ee42496
Refactor Union_find.merge
voodoos Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Tue Jun 23 12:15:42 CEST 2026
- Fix signature-help with type aliases (#2067, fixes #1927)
- Fix locate on punned let bindings, to use the common identifier as the
expression (instead of the pattern) (#2066)
+ index format
- Use a LRU to reduce memory usage when indexing. Change the way small
values are stored. Make sub-indexes paths relative to the working
directory of the indexer. (#2079)
+ test suite
- Remove the FIXME line for #1404 as the issue was already fixed and add two tests (#2073).

Expand Down
17 changes: 9 additions & 8 deletions src/analysis/occurrences.ml
Original file line number Diff line number Diff line change
Expand Up @@ -224,25 +224,26 @@ let get_external_locs ~(config : Mconfig.t) ~current_buffer_path uid :
let lookup_related_uids_in_indexes ~(config : Mconfig.t) uid =
let title = "lookup_related_uids_in_indexes" in
let open Index_format in
let related_uids =
List.fold_left ~init:(Uid_map.empty ()) config.merlin.index_files
~f:(fun acc index_file ->
let store, related_uids =
List.fold_left
~init:(Uid_map.empty (), Uid_map.empty ())
config.merlin.index_files
~f:(fun (store, acc) index_file ->
try
let index = Index_cache.read index_file in
Uid_map.union
(fun _ a b -> Some (Union_find.union a b))
index.related_uids acc
Union_find.merge_union store index.related_uids
index.related_uids_store acc
with
| Index_format.Not_an_index _
| Sys_error _
| Granular_marshal.Outdated_store _
->
log ~title "Could not load index %s" index_file;
acc)
(store, acc))
in
Uid_map.find_opt uid related_uids
|> Option.value_map ~default:[] ~f:(fun x ->
x |> Union_find.get |> Uid_set.to_list)
x |> Union_find.get store |> Uid_set.elements)

let find_linked_uids ~config ~scope ~name uid =
let title = "find_linked_uids" in
Expand Down
145 changes: 145 additions & 0 deletions src/index-format/dbllist.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
type 'a cell =
{ content : 'a; weight : int; mutable prev : 'a cell; mutable next : 'a cell }

type stats =
{ mutable total_cap : int;
mutable promote_count : int;
mutable add_count : int;
mutable discard_count : int;
mutable add_size : int;
mutable discarded_size : int
}

type 'a dbll =
| Nil of int
| List of { first : 'a cell; last : 'a cell; size : int; cap : int }

type 'a t = { mutable dbll : 'a dbll; stats : stats }

exception Action_on_empty_list of string

let pp_stats t =
let size =
match t.dbll with
| Nil _ -> 0
| List l -> l.size
in
Printf.eprintf
"total_cap \t\t: %d\n\
size \t\t: %d\n\
promote_count \t: %d\n\
add_count \t\t: %d\n\
discard_count \t: %d\n\
add_size \t\t: %d\n\
discard_size \t: %d\n\
volume_conservation \t: %d = %d + %d : %b\n\
%!"
t.stats.total_cap size t.stats.promote_count t.stats.add_count
t.stats.discard_count t.stats.add_size t.stats.discarded_size
t.stats.add_size t.stats.discarded_size size
(t.stats.add_size = t.stats.discarded_size + size)

let create cap =
let stats =
{ total_cap = cap;
promote_count = 0;
add_count = 0;
discard_count = 0;
add_size = 0;
discarded_size = 0
}
in
{ dbll = Nil cap; stats }

let add_front t (v, w) =
t.stats.add_count <- t.stats.add_count + 1;
t.stats.add_size <- t.stats.add_size + w;
match t.dbll with
| Nil cap ->
let rec c = { content = v; weight = w; prev = c; next = c } in
t.dbll <- List { first = c; last = c; size = w; cap };
c
| List l ->
let rec new_first =
{ content = v; weight = w; prev = new_first; next = l.first }
in
l.first.prev <- new_first;
t.dbll <-
List { first = new_first; last = l.last; size = l.size + w; cap = l.cap };
new_first

let discard t =
t.stats.discard_count <- t.stats.discard_count + 1;
match t.dbll with
| Nil _ ->
raise
(Action_on_empty_list
"Unable to discard the last element, the doubly linked list is empty.")
| List l ->
if l.first == l.last then (
t.dbll <- Nil l.cap;
t.stats.discarded_size <- t.stats.discarded_size + l.last.weight;
l.last.content)
else
let discarded_value = l.last.content in
let discarded_weight = l.last.weight in
t.stats.discarded_size <- t.stats.discarded_size + discarded_weight;
let new_last = l.last.prev in
(* TODO Should we explicitely disconnect last's pointers ? *)
new_last.next <- new_last;
(* TODO Int.max 0 (l.size - discarded_weight) does seems useless.
We could use an assert to check it. *)
(* Unlinking the discaded cell is not strictly necessary but not doing it
could lead to memory leaks if the user of the cache keeps a reference
to the cell. *)
Comment on lines +93 to +94

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some TODOs here with nice ideas :) (but not essentials)

l.last.next <- l.last;
l.last.prev <- l.last;
t.dbll <-
List
{ first = l.first;
last = new_last;
size = Int.max 0 (l.size - discarded_weight);
cap = l.cap
};
discarded_value

let discard_size t s =
(* this is fold not iter *)
let rec iter acc t =
match t.dbll with
| Nil _ -> acc
| List l -> if l.size + s <= l.cap then acc else iter (discard t :: acc) t
in
iter [] t

let promote t c =
t.stats.promote_count <- t.stats.promote_count + 1;
match t.dbll with
| Nil _ ->
raise
(Action_on_empty_list
"Unable to promote a cell, the doubly linked list is empty.")
| List l ->
if l.first == c then ()
else if l.last == c then (
let new_last = l.last.prev in
new_last.next <- new_last;
let new_first = c in
new_first.next <- l.first;
new_first.prev <- new_first;
l.first.prev <- new_first;
t.dbll <-
List { first = new_first; last = new_last; size = l.size; cap = l.cap })
else
let voisin_prev = c.prev in
let voisin_next = c.next in
voisin_prev.next <- voisin_next;
voisin_next.prev <- voisin_prev;
let new_first = c in
new_first.prev <- new_first;
new_first.next <- l.first;
l.first.prev <- new_first;
t.dbll <-
List { first = new_first; last = l.last; size = l.size; cap = l.cap }

let get c = c.content
26 changes: 26 additions & 0 deletions src/index-format/dbllist.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type 'a cell =
{ content : 'a; weight : int; mutable prev : 'a cell; mutable next : 'a cell }

type stats =
{ mutable total_cap : int;
mutable promote_count : int;
mutable add_count : int;
mutable discard_count : int;
mutable add_size : int;
mutable discarded_size : int
}

type 'a dbll =
| Nil of int
| List of { first : 'a cell; last : 'a cell; size : int; cap : int }

type 'a t = { mutable dbll : 'a dbll; stats : stats }

exception Action_on_empty_list of string

val pp_stats : 'a t -> unit
val create : int -> 'a t
val add_front : 'a t -> 'a * int -> 'a cell
val discard_size : 'a t -> int -> 'a list
val promote : 'a t -> 'a cell -> unit
val get : 'a cell -> 'a
2 changes: 1 addition & 1 deletion src/index-format/dune
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
-open Ocaml_typing
-open Ocaml_utils
-open Merlin_utils)
(libraries ocaml_parsing ocaml_typing ocaml_utils merlin_utils))
(libraries unix ocaml_parsing ocaml_typing ocaml_utils merlin_utils))
1 change: 1 addition & 0 deletions src/index-format/granular_map.ml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module type S = sig
val choose_opt : 'a t -> (key * 'a) option
val iter : (key -> 'a -> unit) -> 'a t -> unit
val iter_in_memory : (key -> 'a -> unit) -> 'a t -> unit

val fold : (key -> 'a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
val map : ('a -> 'b) -> 'a t -> 'b t
val is_empty : 'a t -> bool
Expand Down
Loading
Loading