Skip to content

Commit

Permalink
mother of perf
Browse files Browse the repository at this point in the history
Summary: This diff contains a major rewrite of the cross-module ("merge") phase of Flow's type-checking algorithm, enabling huge performance wins for both init and recheck times (order of magnitude for large-enough code bases).

The main difference is that whereas earlier, all N files would be merged with their recursive dependencies independently in parallel, now the merging is orchestrated, so that files are merged only after all their dependencies are merged. (Cycles are, of course, merged together.)

Overall, this means that shared dependencies are not merged over and over again, but instead only once. (Results of merging are stored and reused.) For long chains of sharing, this leads to a big-O reduction in work (from quadratic to linear).

Furthermore, the memory footprint for results of merging is quite small, since they only need to describe signatures. This leads to very low overhead for the additional (de)serialization between shared memory.

Since the work queue for workers dynamically grows and shrinks in this scheme, simple bucketing (by cutting off chunks from a static queue) doesn't work. This diff adds support for a more general bucketing protocol that signals workers to wait in addition to providing work, and implements such a bucketing protocol for the new merging strategy.

For compressing the results of merging, this diff also implements a context optimizer that walks a context from its exported signature and records only the parts of the context that need to be preserved.

These improvements vastly bring down init times (as well as recheck times).

In addition, some previously slow dependency calculations have now been parallelized, which lead to further vast gains in recheck times (as well as init times).

Entry points to various client commands have been accordingly updated.

A final improvement worth mentioning that is not directly related to perf but was necessary for ensuring correctness is that builtins references are now treated like other module references: every context has its own reference and they are linked during merging (whereas earlier there used to be a single global reference shared by all contexts).

Reviewed By: @bhosmer

Differential Revision: D2320934
  • Loading branch information
avikchaudhuri authored and facebook-github-bot-3 committed Sep 12, 2015
1 parent 8f0aed3 commit 17196e8
Show file tree
Hide file tree
Showing 42 changed files with 1,153 additions and 638 deletions.
19 changes: 10 additions & 9 deletions src/common/errors_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,19 @@ let json_of_loc loc = Loc.(
"end", Json.JInt loc._end.column ]
)

(* warnings before errors, then first reason's position,
then second reason's position. If all positions match then first message,
then second message, etc
TODO may not work for everything... *)
(* first reason's position, then second reason's position, etc.; if all
positions match then first message, then second message, etc. *)
let compare =
let seq k1 k2 = fun () ->
match k1 () with
| 0 -> k2 ()
| i -> i
in
let cmp x1 x2 () =
Pervasives.compare x1 x2
let loc_cmp x1 x2 () =
Loc.compare x1 x2
in
let string_cmp x1 x2 () =
String.compare x1 x2
in
let rec compare_message_lists k = function
| [], [] -> k ()
Expand All @@ -137,11 +138,11 @@ let compare =
| BlameM(_)::_, CommentM(_)::_ -> 1

| CommentM(m1)::rest1, CommentM(m2)::rest2 ->
compare_message_lists (seq k (cmp m1 m2)) (rest1, rest2)
compare_message_lists (seq k (string_cmp m1 m2)) (rest1, rest2)

| BlameM(loc1, m1)::rest1, BlameM(loc2, m2)::rest2 ->
seq (cmp loc1 loc2) (fun () ->
compare_message_lists (seq k (cmp m1 m2)) (rest1, rest2)
seq (loc_cmp loc1 loc2) (fun () ->
compare_message_lists (seq k (string_cmp m1 m2)) (rest1, rest2)
) ()
in
fun (lx, ml1, _) (ly, ml2, _) ->
Expand Down
2 changes: 1 addition & 1 deletion src/parsing/parsing_service_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ let parse workers next init_modes =
let reparse workers files init_modes =
ParserHeap.remove_batch files;
SharedMem.collect `gentle;
let next = Bucket.make_20 (SSet.elements files) in
let next = Bucket.make (SSet.elements files) in
parse workers next init_modes

let get_ast_unsafe file =
Expand Down
1 change: 0 additions & 1 deletion src/server/server.ml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ struct
let flow_options = OptionParser.parse () in
Types_js.init_modes flow_options;
ignore (Flow_js.master_cx ());
ignore (Flow_js.builtins ());
Parsing_service_js.call_on_success SearchService_js.update

let init genv env =
Expand Down
10 changes: 3 additions & 7 deletions src/typing/constraint_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -935,9 +935,6 @@ type context = {
(* map from module names to their types *)
mutable modulemap: Type.t SMap.t;

(* A subset of required modules on which the exported type depends *)
mutable strict_required: SSet.t;

mutable errors: Errors_js.ErrorSet.t;
mutable globals: SSet.t;

Expand All @@ -946,6 +943,7 @@ type context = {
type_table: (Loc.t, Type.t) Hashtbl.t;
annot_table: (Loc.t, Type.t) Hashtbl.t;
}

and module_exports_type =
| CommonJSModule of Loc.t option
| ESModule
Expand All @@ -968,8 +966,6 @@ let new_context ?(checked=false) ?(weak=false) ~file ~_module = {
property_maps = IMap.empty;
modulemap = SMap.empty;

strict_required = SSet.empty;

errors = Errors_js.ErrorSet.empty;
globals = SSet.empty;

Expand Down Expand Up @@ -2793,13 +2789,13 @@ class ['a] type_visitor = object(self)
let acc = self#type_ cx acc value in
acc

method private props cx acc id =
method props cx acc id =
self#smap (self#type_ cx) acc (IMap.find_unsafe id cx.property_maps)

method private type_param cx acc { bound; _ } =
self#type_ cx acc bound

method private fun_type cx acc { this_t; params_tlist; return_t; _} =
method fun_type cx acc { this_t; params_tlist; return_t; _ } =
let acc = self#type_ cx acc this_t in
let acc = self#list (self#type_ cx) acc params_tlist in
let acc = self#type_ cx acc return_t in
Expand Down
10 changes: 5 additions & 5 deletions src/typing/constraint_js.mli
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,6 @@ type context = {
(* map from module names to their types *)
mutable modulemap: Type.t SMap.t;

(* A subset of required modules on which the exported type depends *)
mutable strict_required: SSet.t;

mutable errors: Errors_js.ErrorSet.t;
mutable globals: SSet.t;

Expand All @@ -420,6 +417,7 @@ type context = {
type_table: (Loc.t, Type.t) Hashtbl.t;
annot_table: (Loc.t, Type.t) Hashtbl.t;
}

and module_exports_type =
| CommonJSModule of Loc.t option
| ESModule
Expand Down Expand Up @@ -474,6 +472,8 @@ val loc_of_predicate: Type.predicate -> Loc.t

class ['a] type_visitor: object
(* Only exposing a few methods for now. *)
method type_: context -> 'a -> Type.t -> 'a
method id_: context -> 'a -> ident -> 'a
method type_ : context -> 'a -> Type.t -> 'a
method id_ : context -> 'a -> ident -> 'a
method props : context -> 'a -> ident -> 'a
method fun_type : context -> 'a -> Type.funtype -> 'a
end
Loading

0 comments on commit 17196e8

Please sign in to comment.