I quickly checked how a persistent term cached implementation would compare, which turned out to perform almost equivalent. It seems the :re.version and :erlang.system_info(:endian) values are already cached.
```elixir
defmodule RegexPersistent do
def version do
case :persistent_term.get(__MODULE__, nil) do
nil ->
version = {:re.version(), :erlang.system_info(:endian)}
:persistent_term.put(__MODULE__, version)
version
version ->
version
end
end
defp safe_run(
%Regex{re_pattern: compiled, source: source, re_version: version, opts: compile_opts},
string,
options
) do
case version() do
^version -> :re.run(string, compiled, options)
_ -> :re.run(string, source, translate_options(compile_opts, options))
end
end
def run(%Regex{} = regex, string, options \\ []) when is_binary(string) do
return = Keyword.get(options, :return, :binary)
captures = Keyword.get(options, :capture, :all)
offset = Keyword.get(options, :offset, 0)
case safe_run(regex, string, [{:capture, captures, return}, {:offset, offset}]) do
:nomatch -> nil
:match -> []
{:match, results} -> results
end
end
defp translate_options(<<?u, t::binary>>, acc), do: translate_options(t, [:unicode, :ucp | acc])
defp translate_options(<<?i, t::binary>>, acc), do: translate_options(t, [:caseless | acc])
defp translate_options(<<?x, t::binary>>, acc), do: translate_options(t, [:extended | acc])
defp translate_options(<<?f, t::binary>>, acc), do: translate_options(t, [:firstline | acc])
defp translate_options(<<?U, t::binary>>, acc), do: translate_options(t, [:ungreedy | acc])
defp translate_options(<<?s, t::binary>>, acc),
do: translate_options(t, [:dotall, {:newline, :anycrlf} | acc])
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline | acc])
defp translate_options(<<?r, t::binary>>, acc) do
IO.warn("the /r modifier in regular expressions is deprecated, please use /U instead")
translate_options(t, [:ungreedy | acc])
end
defp translate_options(<<>>, acc), do: acc
defp translate_options(rest, _acc), do: {:error, rest}
end
regex = ~r/^([a-z][a-z0-9\+\-\.]*):/i
re_regex = regex.re_pattern
Benchee.run(%{
"Regex.run/2" => fn -> Regex.run(regex, "foo") end,
"RegexPersistent.run/2" => fn -> RegexPersistent.run(regex, "foo") end,
":re.run/3" => fn -> :re.run("foo", re_regex, [{:capture, :all, :binary}]) end
})
```
Results:
```
:re.run/3 2.72 M 367.06 ns ±3579.21% 333 ns 458 ns
Regex.run/2 1.79 M 557.84 ns ±5817.83% 417 ns 542 ns
RegexPersistent.run/2 1.79 M 558.06 ns ±7018.25% 375 ns 541 ns
Comparison:
:re.run/3 2.72 M
Regex.run/2 1.79 M - 1.52x slower +190.77 ns
RegexPersistent.run/2 1.79 M - 1.52x slower +190.99 ns
```