Skip to content

Inline comprehensions into non-empty bitstrings - #15988

Merged
josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:perf/binary-slice-2
Oct 14, 2026
Merged

josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:perf/binary-slice-2

Conversation

@preciz

@preciz preciz commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Grok Bot, Angtigravity CLI: Gemini 4

This is a proposed solution to the discussion in #15904

I tried to navigate through the tradeoffs but there are some regressions that I couldn't avoid.
Some raised error messages change.

Previously only into: "" was compiled inline and any other
bitstring went through the Collectable protocol. Now binary
generators append the comprehension to the prefix and other
generators start the accumulator with it.

Bench:

# Both modules below are compiled from the SAME source:
#
#   * Before is compiled with elixir_erl_for from upstream 80b229115 (pinned)
#   * After is compiled with elixir_erl_for from HEAD
#
# The source has three functions, one per affected path:
#
#   * binary_slice/2 - upstream's stepped slice, a binary generator
#     into `<<first_byte>>` (spliced binary comprehension)
#   * bjoin/2 - a binary generator into "" (unchanged path) or "ab"
#     (spliced binary comprehension)
#   * join/2 - a list generator into "" (regression check for the
#     `[<<>>]` accumulator seed) or "ab" (list_to_bitstring path)
#
# Run it with this worktree's built Elixir, after `make compile`:
#
#     bin/elixir bench.exs
Mix.install([{:benchee, "== 1.5.1"}])

defmodule CompilerSwap do
  @repo __DIR__
  @src "lib/elixir/src/elixir_erl_for.erl"

  # Compiles elixir_erl_for.erl as of `rev` and loads it into this VM.
  def load_for(rev) do
    {source, 0} = System.cmd("git", ["show", "#{rev}:#{@src}"], cd: @repo)
    tmp = Path.join(System.tmp_dir!(), "elixir_erl_for_#{rev |> String.replace(~r/\W/, "_")}")
    File.mkdir_p!(tmp)
    file = Path.join(tmp, "elixir_erl_for.erl")
    File.write!(file, source)

    include = Path.join(@repo, "lib/elixir/src")
    {:ok, mod, beam} = :compile.file(String.to_charlist(file), [:binary, :report, i: String.to_charlist(include)])
    :code.purge(mod)
    {:module, ^mod} = :code.load_binary(mod, String.to_charlist(file), beam)
    :ok
  end

  def compile_bench_module(module) do
    Code.compile_string("""
    defmodule #{inspect(module)} do
      def binary_slice(binary, first..last//step)
          when is_binary(binary) and step > 0 do
        total = byte_size(binary)

        first = if first < 0, do: max(first + total, 0), else: first
        last = if last < 0, do: last + total, else: last

        amount = last - first + 1

        if first < total and amount > 0 do
          part = binary_part(binary, first, min(amount, total - first))

          if step == 1 do
            part
          else
            <<first_byte, rest::binary>> = part
            for <<_::size(^step - 1)-bytes, byte <- rest>>, into: <<first_byte>>, do: <<byte>>
          end
        else
          ""
        end
      end

      def binary_slice(binary, _.._//_ = range) when is_binary(binary) do
        raise ArgumentError,
              "binary_slice/2 does not accept ranges with negative steps, got: \#{inspect(range)}"
      end

      def bjoin(bin, ""), do: for(<<x <- bin>>, into: "", do: <<x>>)
      def bjoin(bin, _prefix), do: for(<<x <- bin>>, into: "ab", do: <<x>>)

      def join(list, ""), do: for(x <- list, into: "", do: <<x>>)
      def join(list, _prefix), do: for(x <- list, into: "ab", do: <<x>>)
    end
    """)
  end
end

CompilerSwap.load_for("80b229115")
[{Before, before_beam}] = CompilerSwap.compile_bench_module(Before)
CompilerSwap.load_for("HEAD")
[{After, after_beam}] = CompilerSwap.compile_bench_module(After)

# Sanity check: each benchmarked function must call Collectable (from its
# non-empty prefix clause) when compiled by the old compiler, and no function
# may call it when compiled by the new one.
collectable_funs = fn beam ->
  {:beam_file, _, _, _, _, code} = :beam_disasm.file(beam)

  for {:function, name, arity, _, instrs} <- code,
      Enum.any?(instrs, &match?({_, _, {:extfunc, Collectable, _, _}}, &1)),
      do: {name, arity}
end

benched = [binary_slice: 2, bjoin: 2, join: 2]

unless Enum.all?(benched, &(&1 in collectable_funs.(before_beam))) and
         collectable_funs.(after_beam) == [] do
  raise "Before/After were not compiled with the old/new compiler as expected"
end

defmodule Batch do
  # Both implementations use the same batch size for a given input.
  def run(mod, {fun, a, b, iterations}), do: repeat(mod, fun, a, b, iterations)

  defp repeat(_mod, _fun, _a, _b, 0), do: :ok

  defp repeat(mod, fun, a, b, remaining) do
    apply(mod, fun, [a, b])
    repeat(mod, fun, a, b, remaining - 1)
  end
end

bytes = for byte <- 0..255, into: <<>>, do: <<byte>>
small = binary_part(bytes, 0, 16)
large = :binary.copy(bytes, 256)

inputs = %{
  "slice: 16 bytes, step 2" => {:binary_slice, small, 0..-1//2},
  "slice: 16 bytes, step 15 (two output bytes)" => {:binary_slice, small, 0..-1//15},
  "slice: 16 bytes, step 16 (first byte only)" => {:binary_slice, small, 0..-1//16},
  "slice: 64 KiB, step 100" => {:binary_slice, large, 0..-1//100},
  "slice: 64 KiB, step 2" => {:binary_slice, large, 0..-1//2}
}

inputs =
  for {size, prefix} <- [{1, ""}, {16, ""}, {1024, ""}, {65_536, ""}, {1, "ab"}, {16, "ab"}, {1024, "ab"}],
      into: inputs do
    list = Enum.map(1..size, &rem(&1, 256))
    {"join: #{size} elements, into: #{inspect(prefix)}", {:join, list, prefix}}
  end

inputs =
  for {size, prefix} <- [{1, ""}, {16, ""}, {1024, ""}, {1, "ab"}, {1024, "ab"}], into: inputs do
    bin = :binary.copy(bytes, div(size + 255, 256)) |> binary_part(0, size)
    {"bjoin: #{size} bytes, into: #{inspect(prefix)}", {:bjoin, bin, prefix}}
  end

# Verify equivalence and choose batch sizes outside the timed samples.
# Outputs of 1 KiB or more use 100 calls so that a 10-second timing window
# includes many samples. Smaller outputs use 1,000 calls to reduce timer noise.
inputs =
  Map.new(inputs, fn {name, {fun, a, b}} ->
    expected = apply(Before, fun, [a, b])

    unless expected == apply(After, fun, [a, b]) do
      raise "Results differ for #{name}"
    end

    iterations = if byte_size(expected) >= 1024, do: 100, else: 1_000
    {"#{name} [#{iterations} calls/sample]", {fun, a, b, iterations}}
  end)

jobs = %{
  "Old" => &Batch.run(Before, &1),
  "New" => &Batch.run(After, &1)
}

timing = [warmup: 2, time: 10, memory_time: 1, reduction_time: 1]

IO.puts("Timings, memory, and reductions are per batch; batch size appears in each input name.")
IO.puts("Divide by batch size for per-call values. Memory excludes off-heap binary payloads.")

Benchee.run(
  jobs,
  timing ++
    [
      inputs: inputs,
      parallel: 1,
      formatters: [{Benchee.Formatters.Console, extended_statistics: true}]
    ]
)

Results:

Timings, memory, and reductions are per batch; batch size appears in each input name.
Divide by batch size for per-call values. Memory excludes off-heap binary payloads.
Operating System: Linux
CPU Information: AMD Ryzen 7 8845HS w
Number of Available Cores: 16
Available memory: 54.72 GB
Elixir 1.21.0-dev
Erlang 29.1.1
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 10 s
memory time: 1 s
reduction time: 1 s
parallel: 1
inputs: bjoin: 1 bytes, into: "" [1000 calls/sample], bjoin: 1 bytes, into: "ab" [1000 calls/sample], bjoin: 1024 bytes, into: "" [100 calls/sample], bjoin: 1024 bytes, into: "ab" [100 calls/sample], bjoin: 16 bytes, into: "" [1000 calls/sample], join: 1 elements, into: "" [1000 calls/sample], join: 1 elements, into: "ab" [1000 calls/sample], join: 1024 elements, into: "" [100 calls/sample], join: 1024 elements, into: "ab" [100 calls/sample], join: 16 elements, into: "" [1000 calls/sample], join: 16 elements, into: "ab" [1000 calls/sample], join: 65536 elements, into: "" [100 calls/sample], slice: 16 bytes, step 15 (two output bytes) [1000 calls/sample], slice: 16 bytes, step 16 (first byte only) [1000 calls/sample], slice: 16 bytes, step 2 [1000 calls/sample], slice: 64 KiB, step 100 [1000 calls/sample], slice: 64 KiB, step 2 [100 calls/sample]
Estimated total run time: 7 min 56 s
Excluding outliers: false

Benchmarking New with input bjoin: 1 bytes, into: "" [1000 calls/sample] ...
Benchmarking New with input bjoin: 1 bytes, into: "ab" [1000 calls/sample] ...
Benchmarking New with input bjoin: 1024 bytes, into: "" [100 calls/sample] ...
Benchmarking New with input bjoin: 1024 bytes, into: "ab" [100 calls/sample] ...
Benchmarking New with input bjoin: 16 bytes, into: "" [1000 calls/sample] ...
Benchmarking New with input join: 1 elements, into: "" [1000 calls/sample] ...
Benchmarking New with input join: 1 elements, into: "ab" [1000 calls/sample] ...
Benchmarking New with input join: 1024 elements, into: "" [100 calls/sample] ...
Benchmarking New with input join: 1024 elements, into: "ab" [100 calls/sample] ...
Benchmarking New with input join: 16 elements, into: "" [1000 calls/sample] ...
Benchmarking New with input join: 16 elements, into: "ab" [1000 calls/sample] ...
Benchmarking New with input join: 65536 elements, into: "" [100 calls/sample] ...
Benchmarking New with input slice: 16 bytes, step 15 (two output bytes) [1000 calls/sample] ...
Benchmarking New with input slice: 16 bytes, step 16 (first byte only) [1000 calls/sample] ...
Benchmarking New with input slice: 16 bytes, step 2 [1000 calls/sample] ...
Benchmarking New with input slice: 64 KiB, step 100 [1000 calls/sample] ...
Benchmarking New with input slice: 64 KiB, step 2 [100 calls/sample] ...
Benchmarking Old with input bjoin: 1 bytes, into: "" [1000 calls/sample] ...
Benchmarking Old with input bjoin: 1 bytes, into: "ab" [1000 calls/sample] ...
Benchmarking Old with input bjoin: 1024 bytes, into: "" [100 calls/sample] ...
Benchmarking Old with input bjoin: 1024 bytes, into: "ab" [100 calls/sample] ...
Benchmarking Old with input bjoin: 16 bytes, into: "" [1000 calls/sample] ...
Benchmarking Old with input join: 1 elements, into: "" [1000 calls/sample] ...
Benchmarking Old with input join: 1 elements, into: "ab" [1000 calls/sample] ...
Benchmarking Old with input join: 1024 elements, into: "" [100 calls/sample] ...
Benchmarking Old with input join: 1024 elements, into: "ab" [100 calls/sample] ...
Benchmarking Old with input join: 16 elements, into: "" [1000 calls/sample] ...
Benchmarking Old with input join: 16 elements, into: "ab" [1000 calls/sample] ...
Benchmarking Old with input join: 65536 elements, into: "" [100 calls/sample] ...
Benchmarking Old with input slice: 16 bytes, step 15 (two output bytes) [1000 calls/sample] ...
Benchmarking Old with input slice: 16 bytes, step 16 (first byte only) [1000 calls/sample] ...
Benchmarking Old with input slice: 16 bytes, step 2 [1000 calls/sample] ...
Benchmarking Old with input slice: 64 KiB, step 100 [1000 calls/sample] ...
Benchmarking Old with input slice: 64 KiB, step 2 [100 calls/sample] ...
Calculating statistics...
Formatting results...

##### With input bjoin: 1 bytes, into: "" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
Old        12.34 K       81.07 μs    ±40.40%       63.90 μs      195.63 μs
New        11.26 K       88.80 μs    ±38.04%       77.77 μs      187.74 μs

Comparison: 
Old        12.34 K
New        11.26 K - 1.10x slower +7.73 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
Old         54.53 μs     1870.97 μs       122.88 K                 59.19 μs
New         53.72 μs     1522.26 μs       112.17 K                 61.36 μs

Memory usage statistics:

Name    Memory usage
Old        101.56 KB
New        101.56 KB - 1.00x memory usage +0 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
Old           5.07 K
New           5.07 K - 1.00x reduction count +0 K

**All measurements for reduction count were the same**

##### With input bjoin: 1 bytes, into: "ab" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
Old        10.52 K       95.03 μs    ±15.07%       94.28 μs      101.12 μs
New        10.45 K       95.68 μs    ±30.36%       92.73 μs      182.95 μs

Comparison: 
Old        10.52 K
New        10.45 K - 1.01x slower +0.65 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
Old         86.70 μs     1302.42 μs       104.89 K                 93.47 μs
New         60.83 μs     1475.02 μs       104.08 K                 67.78 μs

Memory usage statistics:

Name    Memory usage
Old        218.75 KB
New           125 KB - 0.57x memory usage -93.75000 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
Old          19.14 K
New           6.08 K - 0.32x reduction count -13.06600 K

**All measurements for reduction count were the same**

##### With input bjoin: 1024 bytes, into: "" [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         1.76 K      567.77 μs     ±1.59%      566.30 μs      594.92 μs
Old         1.76 K      569.10 μs     ±7.46%      567.33 μs      588.07 μs

Comparison: 
New         1.76 K
Old         1.76 K - 1.00x slower +1.33 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New        549.94 μs      955.20 μs        17.60 K                564.16 μs
Old        509.05 μs     2060.60 μs        17.56 K                567.76 μs

Memory usage statistics:

Name    Memory usage
New         10.16 KB
Old         10.16 KB - 1.00x memory usage +0 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New         102.81 K
Old         102.81 K - 1.00x reduction count +0 K

**All measurements for reduction count were the same**

##### With input bjoin: 1024 bytes, into: "ab" [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         1.75 K        0.57 ms     ±9.36%        0.57 ms        0.61 ms
Old         0.23 K        4.41 ms     ±4.08%        4.37 ms        4.87 ms

Comparison: 
New         1.75 K
Old         0.23 K - 7.70x slower +3.84 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New          0.52 ms        2.14 ms        17.45 K                  0.57 ms
Old          4.19 ms        6.31 ms         2.27 K                  4.30 ms

Memory usage statistics:

Name    Memory usage
New        0.0160 MB
Old         14.15 MB - 883.00x memory usage +14.13 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New         103.01 K
Old         448.59 K - 4.35x reduction count +345.58 K

**All measurements for reduction count were the same**

##### With input bjoin: 16 bytes, into: "" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         6.28 K      159.18 μs    ±17.53%      154.92 μs      193.70 μs
Old         5.93 K      168.61 μs    ±16.36%      168.67 μs      204.43 μs

Comparison: 
New         6.28 K
Old         5.93 K - 1.06x slower +9.43 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New        129.15 μs     1578.73 μs        62.69 K                184.66 μs
Old        131.51 μs     1566.29 μs        59.17 K                168.97 μs

Memory usage statistics:

Name    Memory usage
New        101.56 KB
Old        101.56 KB - 1.00x memory usage +0 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New          20.07 K
Old          20.07 K - 1.00x reduction count +0 K

**All measurements for reduction count were the same**

##### With input join: 1 elements, into: "" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
Old        22.00 K       45.45 μs    ±20.81%       44.97 μs       50.85 μs
New        19.76 K       50.62 μs    ±25.77%       49.91 μs       54.85 μs

Comparison: 
Old        22.00 K
New        19.76 K - 1.11x slower +5.16 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
Old         42.56 μs     2834.99 μs       218.93 K                 45.06 μs
New         45.75 μs     1629.83 μs       196.69 K                 49.94 μs

Memory usage statistics:

Name    Memory usage
Old         62.50 KB
New         62.50 KB - 1.00x memory usage +0 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
Old          12.04 K
New          12.04 K - 1.00x reduction count +0 K

**All measurements for reduction count were the same**

##### With input join: 1 elements, into: "ab" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New        19.04 K       52.52 μs    ±22.86%       51.82 μs       58.44 μs
Old        11.03 K       90.69 μs    ±15.20%       89.92 μs       99.14 μs

Comparison: 
New        19.04 K
Old        11.03 K - 1.73x slower +38.18 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New         47.37 μs     1485.95 μs       189.58 K                 51.60 μs
Old         83.21 μs      947.22 μs       109.92 K                 90.18 μs

Memory usage statistics:

Name    Memory usage
New         62.50 KB
Old        148.44 KB - 2.38x memory usage +85.94 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New          12.04 K
Old          21.09 K - 1.75x reduction count +9.05 K

**All measurements for reduction count were the same**

##### With input join: 1024 elements, into: "" [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         504.80        1.98 ms    ±11.75%        1.90 ms        2.58 ms
Old         494.90        2.02 ms    ±10.80%        1.93 ms        2.62 ms

Comparison: 
New         504.80
Old         494.90 - 1.02x slower +0.0396 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New          1.83 ms        4.17 ms         5.05 K1.86 ms, 1.91 ms, 1.87 ms
Old          1.84 ms        3.41 ms         4.95 K1.93 ms, 1.93 ms, 1.93 ms

Memory usage statistics:

Name    Memory usage
New          3.91 MB
Old          3.91 MB - 1.00x memory usage +0 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New         373.69 K
Old         373.66 K - 1.00x reduction count -0.02900 K

**All measurements for reduction count were the same**

##### With input join: 1024 elements, into: "ab" [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         499.28        2.00 ms    ±13.26%        1.91 ms        3.32 ms
Old         341.26        2.93 ms     ±7.05%        2.88 ms        4.33 ms

Comparison: 
New         499.28
Old         341.26 - 1.46x slower +0.93 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New          1.79 ms        4.08 ms         4.99 K                  1.88 ms
Old          2.75 ms        4.61 ms         3.41 K2.86 ms, 2.88 ms, 2.90 ms

Memory usage statistics:

Name    Memory usage
New          3.91 MB
Old          6.26 MB - 1.60x memory usage +2.35 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New         373.69 K
Old         527.48 K - 1.41x reduction count +153.79 K

**All measurements for reduction count were the same**

##### With input join: 16 elements, into: "" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
Old         3.42 K      292.39 μs    ±17.35%      289.33 μs      316.69 μs
New         3.32 K      300.87 μs    ±18.76%      297.14 μs      325.89 μs

Comparison: 
Old         3.42 K
New         3.32 K - 1.03x slower +8.48 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
Old        271.26 μs     2050.99 μs        34.16 K     291.27 μs, 286.90 μs
New        280.57 μs     1886.30 μs        33.19 K                295.48 μs

Memory usage statistics:

Name    Memory usage
Old        656.25 KB
New        656.25 KB - 1.00x memory usage +0 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
Old          65.00 K
New          67.00 K - 1.03x reduction count +2 K

**All measurements for reduction count were the same**

##### With input join: 16 elements, into: "ab" [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         3.30 K      303.38 μs    ±15.20%      300.40 μs      327.31 μs
Old         2.23 K      448.40 μs    ±15.06%      443.95 μs      494.18 μs

Comparison: 
New         3.30 K
Old         2.23 K - 1.48x slower +145.02 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New        278.06 μs     1936.89 μs        32.92 K                298.71 μs
Old        412.95 μs     2362.35 μs        22.28 K     447.83 μs, 442.99 μs

Memory usage statistics:

Name    Memory usage
New          0.65 MB
Old          1.08 MB - 1.66x memory usage +0.43 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New          67.01 K
Old          94.99 K - 1.42x reduction count +27.98 K

**All measurements for reduction count were the same**

##### With input join: 65536 elements, into: "" [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New           5.83      171.53 ms     ±2.78%      172.02 ms      181.08 ms
Old           5.39      185.68 ms     ±2.13%      185.49 ms      194.08 ms

Comparison: 
New           5.83
Old           5.39 - 1.08x slower +14.15 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New        163.29 ms      181.08 ms             59                     None
Old        178.38 ms      194.08 ms             54                     None

Memory usage statistics:

Name    Memory usage
New        250.01 MB
Old        250.01 MB - 1.00x memory usage +0 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name         average  deviation         median         99th %
New          23.17 M     ±0.02%        23.17 M        23.17 M
Old          23.18 M     ±0.04%        23.18 M        23.19 M

Comparison: 
New          23.17 M
Old          23.18 M - 1.00x reduction count +0.0112 M

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New          23.16 M        23.17 M              6                     None
Old          23.17 M        23.19 M              6                     None

##### With input slice: 16 bytes, step 15 (two output bytes) [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         8.69 K      115.02 μs    ±20.51%      116.44 μs      144.64 μs
Old         8.10 K      123.45 μs    ±20.77%      121.99 μs      134.08 μs

Comparison: 
New         8.69 K
Old         8.10 K - 1.07x slower +8.44 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New         78.22 μs     1118.84 μs        86.32 K                117.34 μs
Old        107.38 μs     1057.62 μs        80.77 K                121.71 μs

Memory usage statistics:

Name    Memory usage
New           125 KB
Old        312.50 KB - 2.50x memory usage +187.50 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New           6.02 K
Old          19.04 K - 3.16x reduction count +13.02 K

**All measurements for reduction count were the same**

##### With input slice: 16 bytes, step 16 (first byte only) [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
Old        12.81 K       78.05 μs    ±22.70%       77.03 μs       84.79 μs
New         9.46 K      105.76 μs    ±19.71%      108.50 μs      127.00 μs

Comparison: 
Old        12.81 K
New         9.46 K - 1.36x slower +27.71 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
Old         69.16 μs      760.72 μs       127.61 K                 76.10 μs
New         73.02 μs     1054.93 μs        92.94 K                108.62 μs

Memory usage statistics:

Name    Memory usage
Old        171.88 KB
New           125 KB - 0.73x memory usage -46.87500 KB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
Old          14.03 K
New           5.02 K - 0.36x reduction count -9.00600 K

**All measurements for reduction count were the same**

##### With input slice: 16 bytes, step 2 [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         6.55 K      152.76 μs    ±15.81%      151.34 μs      176.31 μs
Old         3.13 K      319.01 μs    ±18.04%      314.07 μs      355.53 μs

Comparison: 
New         6.55 K
Old         3.13 K - 2.09x slower +166.25 μs

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New        117.65 μs     1297.29 μs        65.06 K                150.45 μs
Old        290.42 μs     2088.50 μs        31.30 K309.73 μs, 309.74 μs, 309

Memory usage statistics:

Name    Memory usage
New         0.122 MB
Old          1.07 MB - 8.75x memory usage +0.95 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New          12.02 K
Old          39.42 K - 3.28x reduction count +27.40 K

**All measurements for reduction count were the same**

##### With input slice: 64 KiB, step 100 [1000 calls/sample] #####
Name           ips        average  deviation         median         99th %
New         224.94        4.45 ms     ±3.66%        4.40 ms        5.74 ms
Old          32.21       31.04 ms     ±5.95%       30.47 ms       37.88 ms

Comparison: 
New         224.94
Old          32.21 - 6.98x slower +26.60 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New          4.27 ms        6.00 ms         2.25 K4.40 ms, 4.40 ms, 4.39 ms
Old         28.69 ms       41.67 ms            323                     None

Memory usage statistics:

Name    Memory usage
New         0.160 MB
Old         90.20 MB - 563.00x memory usage +90.04 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name Reduction count
New           0.66 M
Old           2.76 M - 4.19x reduction count +2.10 M

**All measurements for reduction count were the same**

##### With input slice: 64 KiB, step 2 [100 calls/sample] #####
Name           ips        average  deviation         median         99th %
New          49.74       20.11 ms     ±1.41%       20.03 ms       21.44 ms
Old           4.95      201.88 ms     ±4.26%      200.96 ms      237.89 ms

Comparison: 
New          49.74
Old           4.95 - 10.04x slower +181.77 ms

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New         19.72 ms       21.66 ms            498       20.02 ms, 20.08 ms
Old        189.36 ms      237.89 ms             50                     None

Memory usage statistics:

Name    Memory usage
New        0.0160 MB
Old        450.04 MB - 28089.52x memory usage +450.03 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name         average  deviation         median         99th %
New           3.28 M     ±0.00%         3.28 M         3.28 M
Old          13.78 M     ±0.04%        13.78 M        13.79 M

Comparison: 
New           3.28 M
Old          13.78 M - 4.20x reduction count +10.50 M

Extended statistics: 

Name         minimum        maximum    sample size                     mode
New           3.28 M         3.28 M             50                   3.28 M
Old          13.77 M        13.79 M              6                     None

Previously only `into: ""` was compiled inline and any other
bitstring went through the Collectable protocol. Now binary
generators append the comprehension to the prefix and other
generators start the accumulator with it.
@josevalim
josevalim merged commit e8adeee into elixir-lang:main Oct 14, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants