Optimize Inspect.List.keyword?/1 by using Atom.to_string/1#15631
Merged
Conversation
Atom.to_charlist/1 allocates a new list per atom key when checking if a keyword list contains module names. Atom.to_string/1 produces a binary that can be pattern matched directly, avoiding O(n) temporary allocations when inspecting large maps and keyword lists. Benchmark (n=10): keyword?/1 (10K): 0.62ms -> 0.48ms (1.3x) keyword?/1 (50K): 2.72ms -> 1.69ms (1.6x) inspect map (10K): 2.59ms -> 1.52ms (1.7x) inspect map (50K): 8.85ms -> 5.94ms (1.5x) Assisted-by: opencode:deepseek-v4-flash
Contributor
Author
|
bechmark Code.require_file("../lib/elixir/test/elixir/test_helper.exs", __DIR__)
defmodule InspectBench do
@kw_10k Enum.map(1..10_000, fn i -> {String.to_atom("key_#{i}"), i} end)
@kw_50k Enum.map(1..50_000, fn i -> {String.to_atom("key_#{i}"), i} end)
@map_10k Map.new(1..10_000, fn i -> {String.to_atom("key_#{i}"), i} end)
@map_50k Map.new(1..50_000, fn i -> {String.to_atom("key_#{i}"), i} end)
def run(n \\ 10) do
IO.puts("\n=== Inspect Performance Benchmark (n=#{n}) ===\n")
header()
results = [
measure("keyword?/1 (10K)", n, fn -> Inspect.List.keyword?(@kw_10k) end),
measure("keyword?/1 (50K)", n, fn -> Inspect.List.keyword?(@kw_50k) end),
measure("inspect kwlist (10K)", n, fn -> inspect(@kw_10k) end),
measure("inspect kwlist (50K)", n, fn -> inspect(@kw_50k) end),
measure("inspect map (10K)", n, fn -> inspect(@map_10k) end),
measure("inspect map (50K)", n, fn -> inspect(@map_50k) end)
]
IO.puts("\nDone.")
results
end
defp header do
IO.puts(String.pad_trailing("Test", 28) <>
String.pad_trailing("Avg (ms)", 12) <>
String.pad_trailing("Min (ms)", 12) <>
String.pad_trailing("Max (ms)", 12))
IO.puts(String.duplicate("─", 64))
end
defp measure(name, n, fun) do
fun.() # warmup
times = for _ <- 1..n, do: :timer.tc(fun) |> elem(0)
avg = Enum.sum(times) / n / 1000
min = Enum.min(times) / 1000
max = Enum.max(times) / 1000
IO.puts(String.pad_trailing(name, 28) <>
String.pad_trailing(Float.to_string(Float.round(avg, 2)), 12) <>
String.pad_trailing(Float.to_string(Float.round(min, 2)), 12) <>
String.pad_trailing(Float.to_string(Float.round(max, 2)), 12))
{name, times}
end
end
InspectBench.run() |
josevalim
reviewed
Jul 18, 2026
Member
|
💚 💙 💜 💛 ❤️ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Optimize
Inspect.List.keyword?/1by replacingAtom.to_charlist/1withAtom.to_string/1+ binary pattern matching.Problem
Inspect.List.keyword?/1walks all key-value pairs to check if any key is an Elixir module name (starts with"Elixir."). For each atom key, it callsAtom.to_charlist/1, which allocates a new list on the heap. For a map with 10,000 atom-keyed entries, this is 10,000 temporary allocations that are immediately discarded.Fix
Use
Atom.to_string/1+match?("Elixir." <> _, ...)instead. Binaries are cheaper to allocate and the pattern match is done directly on the binary representation without intermediate list creation.Benchmark (n=10)
keyword?/1(10K keys)keyword?/1(50K keys)inspectmap (10K entries)inspectmap (50K entries)Assisted-by: opencode:deepseek-v4-flash