diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index d9c6a34b4..fd3be1a01 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -437,7 +437,7 @@ let linters_def_default_file = linters_def_dir ^/ "linters.al" let wrappers_dir = lib_dir ^/ "wrappers" -let ncpu = Setcore.numcores () +let ncpu = Utils.numcores let os_type = match Sys.os_type with "Win32" -> Win32 | "Cygwin" -> Cygwin | _ -> Unix diff --git a/infer/src/base/ProcessPool.ml b/infer/src/base/ProcessPool.ml index 05086737b..21d2a0932 100644 --- a/infer/src/base/ProcessPool.ml +++ b/infer/src/base/ProcessPool.ml @@ -378,7 +378,7 @@ let fork_child ~file_lock ~child_prelude ~slot (updates_r, updates_w) ~f ~epilog Unix.close updates_r ; Unix.close to_child_w ; (* Pin to a core. [setcore] does the modulo for us. *) - Setcore.setcore slot ; + Utils.set_best_cpu_for slot ; ProcessPoolState.in_child := true ; ProcessPoolState.reset_pid () ; child_prelude () ; diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index abed01c20..094662e88 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -466,3 +466,41 @@ let iter_infer_deps ~project_root ~f infer_deps_file = List.iter ~f:one_line lines | Error error -> Die.die InternalError "Couldn't read deps file '%s': %s" infer_deps_file error + + +let physical_cores () = + with_file_in "/proc/cpuinfo" ~f:(fun ic -> + let physical_or_core_regxp = + Re.Str.regexp "\\(physical id\\|core id\\)[^0-9]+\\([0-9]+\\).*" + in + let rec loop max_socket_id max_core_id = + match In_channel.input_line ~fix_win_eol:true ic with + | None -> + (max_socket_id + 1, max_core_id + 1) + | Some line when Re.Str.string_match physical_or_core_regxp line 0 -> ( + let value = Re.Str.matched_group 2 line |> int_of_string in + match Re.Str.matched_group 1 line with + | "physical id" -> + loop (max value max_socket_id) max_core_id + | "core id" -> + loop max_socket_id (max value max_core_id) + | _ -> + L.die InternalError "Couldn't parse line '%s' from /proc/cpuinfo." line ) + | Some _ -> + loop max_socket_id max_core_id + in + let sockets, cores_per_socket = loop 0 0 in + sockets * cores_per_socket ) + + +let cpus = Setcore.numcores () + +let numcores = + match Version.build_platform with Darwin | Windows -> cpus / 2 | Linux -> physical_cores () + + +let set_best_cpu_for worker_id = + let threads_per_core = cpus / numcores in + let chosen_core = worker_id * threads_per_core % numcores in + let chosen_thread_in_core = worker_id * threads_per_core / numcores in + Setcore.setcore ((chosen_core * threads_per_core) + chosen_thread_in_core) diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 1109fac9d..1238f3a5d 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -167,3 +167,11 @@ val get_available_memory_MB : unit -> int option val iter_infer_deps : project_root:string -> f:(string -> unit) -> string -> unit (** Parse each line of the given infer_deps.txt file (split on tabs, assume 3 elements per line) and run [f] on the third element. [project_root] is an argument to avoid dependency cycles. *) + +val numcores : int +(** - On Linux return the number of physical cores (sockets * cores per socket). + - On Darwin and Windows returns half of the number of CPUs since most processors have 2 hardware + threads per core. *) + +val set_best_cpu_for : int -> unit +(** Pins processes to CPUs aiming to saturate physical cores evenly *) diff --git a/infer/src/dune.common.in b/infer/src/dune.common.in index 4ab00a8cc..9e64e189c 100644 --- a/infer/src/dune.common.in +++ b/infer/src/dune.common.in @@ -76,6 +76,7 @@ let common_libraries = ; "ocamlgraph" ; "oUnit" ; "parmap" + ; "re" ; "sqlite3" ; "str" ; "unix"