From 8b9bf62654bb1fd3bdaaa7f240f0b5f3755b8a8a Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 6 Mar 2026 13:31:33 +0100 Subject: [PATCH 1/2] Reduce memory usage in IEx autocomplete module listing Use :erlang.loaded/0 instead of :code.all_loaded/0 since we only need module names, not file paths. The latter copies the full path for every loaded module, which on production systems with many dependencies caused significant memory spikes (measured 2.8MB -> 172KB on a real pod). Also replace Enum.sort/1 |> Enum.dedup/1 with :lists.usort/1 to eliminate one intermediate list allocation. Co-Authored-By: Claude Opus 4.6 --- lib/iex/lib/iex/autocomplete.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index 830ae173322..9e2dc98ca3b 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -604,8 +604,7 @@ defmodule IEx.Autocomplete do defp match_modules(hint, elixir_root?) do get_modules(elixir_root?) - |> Enum.sort() - |> Enum.dedup() + |> :lists.usort() |> Enum.drop_while(&(not String.starts_with?(&1, hint))) |> Enum.take_while(&String.starts_with?(&1, hint)) end @@ -615,7 +614,7 @@ defmodule IEx.Autocomplete do end defp get_modules(false) do - modules = Enum.map(:code.all_loaded(), &Atom.to_string(elem(&1, 0))) + modules = Enum.map(:erlang.loaded(), &Atom.to_string/1) case :code.get_mode() do :interactive -> modules ++ get_modules_from_applications() From ae79f84752b0b104e418a6d2d113ed46e0a31dce Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 6 Mar 2026 14:14:35 +0100 Subject: [PATCH 2/2] Avoid String.split per module in IEx autocomplete Replace String.split/Enum.at/length with binary_part and :binary.match to extract submodule names. This avoids allocating intermediate lists of string fragments for every matching module. Co-Authored-By: Claude Opus 4.6 --- lib/iex/lib/iex/autocomplete.ex | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index 9e2dc98ca3b..0c926045235 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -509,19 +509,25 @@ defmodule IEx.Autocomplete do end defp match_elixir_modules(module, hint) do - name = Atom.to_string(module) - depth = length(String.split(name, ".")) + 1 - base = name <> "." <> hint + prefix = Atom.to_string(module) <> "." + prefix_size = byte_size(prefix) + base = prefix <> hint for mod <- match_modules(base, module == Elixir), - parts = String.split(mod, "."), - depth <= length(parts), - name = Enum.at(parts, depth - 1), + rest = binary_part(mod, prefix_size, byte_size(mod) - prefix_size), + name = elixir_submodule_name(rest), valid_alias_piece?("." <> name), uniq: true, do: %{kind: :module, name: name} end + defp elixir_submodule_name(rest) do + case :binary.match(rest, ".") do + {pos, _} -> binary_part(rest, 0, pos) + :nomatch -> rest + end + end + defp valid_alias_piece?(<>) when char in ?A..?Z, do: valid_alias_rest?(rest)