From 443903b936ee74888e38babda8e32057ab7f5757 Mon Sep 17 00:00:00 2001 From: preciz Date: Fri, 2 Oct 2026 11:23:59 +0200 Subject: [PATCH] Optimize String.chunk with uniform fast-path and sub-binary slicing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add fast-paths for 100% valid and printable strings to return [string] immediately, avoiding the chunk loop and allocations entirely for uniform inputs. In the fallback chunk loop: - Reuse the first decoded codepoint and classification, eliminating redundant decoding and the intermediate wrapper. - Replace repeated <> binary concatenation with zero-copy sub-binary slicing via binary_part/3, removing intermediate binary reallocations. Benchee / OTP 29 (1s warmup, 3s time, 1s memory, pre_check: :all_same): | Input | Time parent -> patch (speedup) | Memory parent -> patch (change) | | --- | ---: | ---: | | 11B valid | 748.97 ns -> 59.41 ns (12.61x) | 2.30 KB -> 0.05 KB (-97.6%) | | 11B printable | 856.65 ns -> 74.50 ns (11.50x) | 2.30 KB -> 0.05 KB (-97.6%) | | 1200B valid | 52.00 μs -> 1.44 μs (36.12x) | 239.61 KB -> 0.05 KB (-99.98%) | | 1200B printable | 71.95 μs -> 4.37 μs (16.47x) | 239.61 KB -> 0.05 KB (-99.98%) | | 1200B mixed valid/invalid | 69.06 μs -> 40.80 μs (1.69x) | 215.33 KB -> 181.38 KB (-15.8%) | | 1200B mixed printable | 85.80 μs -> 45.02 μs (1.91x) | 245.34 KB -> 202.66 KB (-17.4%) | Assisted-by: Gemini:3.8-Flash --- lib/elixir/lib/string.ex | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 50e959881d..7a613bedcc 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -2150,31 +2150,36 @@ defmodule String do def chunk("", _), do: [] - def chunk(string, trait) when is_binary(string) and trait in [:valid, :printable] do - {cp, _} = next_codepoint(string) - pred_fn = make_chunk_pred(trait) - do_chunk(string, pred_fn.(cp), pred_fn) + def chunk(string, :valid) when is_binary(string) do + if valid?(string), do: [string], else: chunk_fallback(string, &valid?/1) end - defp do_chunk(string, flag, pred_fn), do: do_chunk(string, [], <<>>, flag, pred_fn) + def chunk(string, :printable) when is_binary(string) do + if printable?(string), do: [string], else: chunk_fallback(string, &printable?/1) + end - defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc) + defp chunk_fallback(string, pred_fn) do + {cp, rest} = next_codepoint(string) + do_chunk(rest, string, 0, byte_size(cp), pred_fn.(cp), pred_fn, []) + end - defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk]) + defp do_chunk(<<>>, orig, start, len, _flag, _pred_fn, acc) do + Enum.reverse(acc, [binary_part(orig, start, len)]) + end - defp do_chunk(string, acc, chunk, flag, pred_fn) do + defp do_chunk(string, orig, start, len, flag, pred_fn, acc) do {cp, rest} = next_codepoint(string) + cp_flag = pred_fn.(cp) - if pred_fn.(cp) != flag do - do_chunk(rest, [chunk | acc], cp, not flag, pred_fn) + if cp_flag == flag do + do_chunk(rest, orig, start, len + byte_size(cp), flag, pred_fn, acc) else - do_chunk(rest, acc, chunk <> cp, flag, pred_fn) + do_chunk(rest, orig, start + len, byte_size(cp), cp_flag, pred_fn, [ + binary_part(orig, start, len) | acc + ]) end end - defp make_chunk_pred(:valid), do: &valid?/1 - defp make_chunk_pred(:printable), do: &printable?/1 - @doc ~S""" Returns Unicode graphemes in the string as per Extended Grapheme Cluster algorithm.