File 1511-Limit-peak-memory-use-while-loading-code-on-small-sy.patch of Package erlang
From 1c8065845ed9b8d2273c910099e21271297f15e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Fri, 24 Jan 2025 07:14:18 +0100
Subject: [PATCH] Limit peak memory use while loading code on small systems
In order to start the runtime system as fast as possible, modules
known to be always needed to be loaded are loaded in parallel.
When those modules have been loaded, the `error_handler` mechanism
kicks in to load modules on demand.
However, loading code in parallel will increase the peak memory usage
while loading, which can be serious issue for embedded system with a
limited amount of memory.
This commit adds an heuristic in the `init` module to disable parallel
loading when it seems probable that the system has limited
memory. Parallel loading is disabled if running on a 32-bit CPU with
a single core. A few calls to `erlang:garbage_collect/0` are also
added to make sure that binaries holding BEAM code are deallocated as
soon as possible.
While at it, the number of processes spawned by `erl_prim_loader` when
fetching and preparing BEAM files has been changed to 1.5 times the
number of schedulers, and capped at 32 processes. It used to be 32
processes regardless of the number of cores.
---
erts/preloaded/ebin/erl_prim_loader.beam | Bin 23940 -> 24092 bytes
erts/preloaded/ebin/init.beam | Bin 27600 -> 27956 bytes
erts/preloaded/src/erl_prim_loader.erl | 15 +++++++++------
erts/preloaded/src/init.erl | 19 +++++++++++++++++++
lib/kernel/src/code.erl | 4 +++-
5 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl
index 9399d1e5ac..c763996f51 100644
--- a/erts/preloaded/src/erl_prim_loader.erl
+++ b/erts/preloaded/src/erl_prim_loader.erl
@@ -483,6 +483,7 @@ loop(St0, Parent, Paths) ->
ok;
{Resp,#state{}=St1} ->
Pid ! {self(),Resp},
+ erlang:garbage_collect(),
loop(St1, Parent, Paths);
{_,State2,_} ->
exit({bad_state,Req,State2})
@@ -691,18 +692,20 @@ efile_gm_recv(N, Ref, Succ, Fail) ->
end.
efile_gm_spawn(ParentRef, Ms, Process, Paths) ->
- efile_gm_spawn_1(0, Ms, ParentRef, Process, Paths).
+ S = erlang:system_info(schedulers_online),
+ MaxN = min(S + (S bsr 1), 32),
+ efile_gm_spawn_1(0, MaxN, Ms, ParentRef, Process, Paths).
-efile_gm_spawn_1(N, Ms, ParentRef, Process, Paths) when N >= 32 ->
+efile_gm_spawn_1(N, MaxN, Ms, ParentRef, Process, Paths) when N > MaxN ->
receive
{'DOWN',_,process,_,_} ->
- efile_gm_spawn_1(N-1, Ms, ParentRef, Process, Paths)
+ efile_gm_spawn_1(N-1, MaxN, Ms, ParentRef, Process, Paths)
end;
-efile_gm_spawn_1(N, [M|Ms], ParentRef, Process, Paths) ->
+efile_gm_spawn_1(N, MaxN, [M|Ms], ParentRef, Process, Paths) ->
Get = fun() -> efile_gm_get(Paths, M, ParentRef, Process) end,
_ = spawn_monitor(Get),
- efile_gm_spawn_1(N+1, Ms, ParentRef, Process, Paths);
-efile_gm_spawn_1(_, [], _, _, _) ->
+ efile_gm_spawn_1(N+1, MaxN, Ms, ParentRef, Process, Paths);
+efile_gm_spawn_1(_, _, [], _, _, _) ->
ok.
efile_gm_get(Paths, Mod, ParentRef, Process) ->
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index aa0fcad347..0a30fda9bb 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -1359,6 +1359,18 @@ eval_script(What, #es{}) ->
load_modules(Mods0, Init) ->
Mods = [M || M <- Mods0, not erlang:module_loaded(M)],
F = prepare_loading_fun(),
+ case has_small_memory() of
+ true ->
+ %% Load one module at the time to reduce the peak memory
+ %% usage.
+ _ = [do_load_modules([M], F, Init) || M <- Mods],
+ ok;
+ false ->
+ %% Load the modules in parallel.
+ do_load_modules(Mods, F, Init)
+ end.
+
+do_load_modules(Mods, F, Init) ->
case erl_prim_loader:get_modules(Mods, F) of
{ok,{Prep0,[]}} ->
Prep = [Code || {_,{prepared,Code,_}} <- Prep0],
@@ -1394,6 +1406,13 @@ prepare_loading_fun() ->
end
end.
+has_small_memory() ->
+ %% Heuristic for small memory. If true, we'll try to preserve
+ %% memory by not loading code in parallel.
+ (erlang:system_info(wordsize) =:= 4 andalso
+ erlang:system_info(schedulers_online) =:= 1) orelse
+ erlang:system_info(debug_compiled).
+
make_path(Pa, Pz, Path, Vars) ->
append([Pa,append([fix_path(Path,Vars),Pz])]).
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 89ab3433e1..23ce452f0b 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -569,7 +569,9 @@ ensure_loaded(Mod) when is_atom(Mod) ->
call({load_error, Mod, Ref}),
Error;
Prepared ->
- call({load_ok, Prepared, Mod, File, Ref})
+ Res = call({load_ok, Prepared, Mod, File, Ref}),
+ erlang:garbage_collect(),
+ Res
end
end;
embedded ->
--
2.43.0