From 36d004ae3a0561623e7e99038aebaeaa147f5156 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 6 Mar 2026 20:43:53 +0100 Subject: [PATCH 1/2] Filter modules during collection in IEx autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of collecting all modules as strings, sorting the full list, and then linearly scanning for matching prefixes, filter modules during collection and only sort the (much smaller) set of matches. Benchmarked with 261 loaded modules, compared against the original code (before the previous two optimizations) and the current code: match_modules("Elixir.Enum"): before PR: 333 µs, 13.6 KB heap current: 91 µs, 1.6 KB heap proposed: 30 µs, 0.5 KB heap (11x faster, 26x less heap) match_modules("er"): before PR: 345 µs, 13.8 KB heap current: 97 µs, 2.9 KB heap proposed: 39 µs, 0.4 KB heap (9x faster, 33x less heap) Co-Authored-By: Claude Opus 4.6 --- lib/iex/lib/iex/autocomplete.ex | 45 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index 0c926045235..85264949b23 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -609,31 +609,38 @@ defmodule IEx.Autocomplete do end defp match_modules(hint, elixir_root?) do - get_modules(elixir_root?) - |> :lists.usort() - |> Enum.drop_while(&(not String.starts_with?(&1, hint))) - |> Enum.take_while(&String.starts_with?(&1, hint)) - end - - defp get_modules(true) do - ["Elixir.Elixir"] ++ get_modules(false) - end + modules = + if elixir_root? do + acc = + for mod <- :erlang.loaded(), + str = Atom.to_string(mod), + String.starts_with?(str, hint), + do: str + + if String.starts_with?("Elixir.Elixir", hint), do: ["Elixir.Elixir" | acc], else: acc + else + for mod <- :erlang.loaded(), + str = Atom.to_string(mod), + String.starts_with?(str, hint), + do: str + end - defp get_modules(false) do - modules = Enum.map(:erlang.loaded(), &Atom.to_string/1) + modules = + case :code.get_mode() do + :interactive -> modules ++ match_modules_from_applications(hint) + _otherwise -> modules + end - case :code.get_mode() do - :interactive -> modules ++ get_modules_from_applications() - _otherwise -> modules - end + :lists.usort(modules) end - defp get_modules_from_applications do + defp match_modules_from_applications(hint) do for [app] <- loaded_applications(), {:ok, modules} = :application.get_key(app, :modules), - module <- modules do - Atom.to_string(module) - end + module <- modules, + str = Atom.to_string(module), + String.starts_with?(str, hint), + do: str end defp loaded_applications do From edaae3a7c493def3e35d54f2402df74c93bb61c8 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 6 Mar 2026 20:46:20 +0100 Subject: [PATCH 2/2] remove duplicated code --- lib/iex/lib/iex/autocomplete.ex | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index 85264949b23..5cc8adf794b 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -610,20 +610,15 @@ defmodule IEx.Autocomplete do defp match_modules(hint, elixir_root?) do modules = - if elixir_root? do - acc = - for mod <- :erlang.loaded(), - str = Atom.to_string(mod), - String.starts_with?(str, hint), - do: str - - if String.starts_with?("Elixir.Elixir", hint), do: ["Elixir.Elixir" | acc], else: acc - else - for mod <- :erlang.loaded(), - str = Atom.to_string(mod), - String.starts_with?(str, hint), - do: str - end + for mod <- :erlang.loaded(), + str = Atom.to_string(mod), + String.starts_with?(str, hint), + do: str + + modules = + if elixir_root? and String.starts_with?("Elixir.Elixir", hint), + do: ["Elixir.Elixir" | modules], + else: modules modules = case :code.get_mode() do