Permalink
Browse files

Cache the AST on definitions

This speeds up the compilation time from 10% to 15%
measured across different projects.
  • Loading branch information...
josevalim committed Jun 29, 2017
1 parent 6aeea8a commit 8518652decbf3127a161db2612fa69b889bab25c
View
@@ -3289,8 +3289,17 @@ defmodule Kernel do
result
end
{escaped, _} = :elixir_quote.escape(block, false)
module_vars = module_vars(env.vars, 0)
escaped =
case env do
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
integer = Kernel.LexicalTracker.write_cache(pid, block)
quote do: Kernel.LexicalTracker.read_cache(unquote(pid), unquote(integer))
%{} ->
{escaped, _} = :elixir_quote.escape(block, false)
escaped
end
module_vars = module_vars(env.vars, 0)
quote do
unquote(with_alias)
@@ -3554,19 +3563,30 @@ defmodule Kernel do
end
defp define(kind, call, expr, env) do
assert_module_scope(env, kind, 2)
module = assert_module_scope(env, kind, 2)
assert_no_function_scope(env, kind, 2)
table = :elixir_module.data_table(module)
{escaped_call, unquoted_call} = :elixir_quote.escape(call, true)
{escaped_expr, unquoted_expr} = :elixir_quote.escape(expr, true)
{call, unquoted_call} = :elixir_quote.escape(call, true)
{expr, unquoted_expr} = :elixir_quote.escape(expr, true)
escaped_expr =
case unquoted_expr do
true ->
escaped_expr
false ->
key = {:cache_def, :erlang.unique_integer()}
:ets.insert(table, {key, expr})
quote do: :ets.lookup_element(unquote(table), unquote(key), 2)
end
# Do not check clauses if any expression was unquoted
check_clauses = not(unquoted_expr or unquoted_call)
pos = :elixir_locals.cache_env(env)
pos = :elixir_locals.cache_env(table, env)
quote do
:elixir_def.store_definition(unquote(kind), unquote(check_clauses),
unquote(call), unquote(expr), unquote(pos))
unquote(escaped_call), unquote(escaped_expr), unquote(pos))
end
end
@@ -4594,7 +4614,7 @@ defmodule Kernel do
defp assert_module_scope(env, fun, arity) do
case env.module do
nil -> raise ArgumentError, "cannot invoke #{fun}/#{arity} outside module"
_ -> :ok
mod -> mod
end
end
@@ -81,6 +81,18 @@ defmodule Kernel.LexicalTracker do
:gen_server.cast(pid, {:alias_dispatch, module})
end
@doc false
def write_cache(pid, value) do
key = :erlang.unique_integer()
:gen_server.cast(pid, {:write_cache, key, value})
key
end
@doc false
def read_cache(pid, key) do
:gen_server.call(pid, {:read_cache, key}, @timeout)
end
@doc false
def collect_unused_imports(pid) do
unused(pid, :import)
@@ -99,7 +111,7 @@ defmodule Kernel.LexicalTracker do
def init(dest) do
{:ok, %{directives: %{}, references: %{}, compile: %{},
runtime: %{}, dest: dest}}
runtime: %{}, dest: dest, cache: %{}}}
end
@doc false
@@ -124,6 +136,14 @@ defmodule Kernel.LexicalTracker do
{:reply, state.dest, state}
end
def handle_call({:read_cache, key}, _from, %{cache: cache} = state) do
{:reply, Map.fetch!(cache, key), state}
end
def handle_cast({:write_cache, key, value}, %{cache: cache} = state) do
{:noreply, Map.put(state, :cache, Map.put(cache, key, value))}
end
def handle_cast({:remote_reference, module, mode}, state) do
{:noreply, %{state | references: add_reference(state.references, module, mode)}}
end
@@ -210,16 +210,6 @@ defmodule Module.LocalsTracker do
last
end
@doc false
def cache_env(pid, env) do
:gen_server.call(pid, {:cache_env, env}, @timeout)
end
@doc false
def get_cached_env(pid, ref) do
:gen_server.call(pid, {:get_cached_env, ref}, @timeout)
end
# Stops the gen server
@doc false
def stop(pid) do
@@ -231,64 +221,48 @@ defmodule Module.LocalsTracker do
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
{:ok, {d, []}}
end
@doc false
def handle_call({:cache_env, env}, _from, {d, cache}) do
case cache do
[{i, ^env} | _] ->
{:reply, i, {d, cache}}
t ->
i = length(t)
{:reply, i, {d, [{i, env} | t]}}
end
end
def handle_call({:get_cached_env, ref}, _from, {_, cache} = state) do
{^ref, env} = :lists.keyfind(ref, 1, cache)
{:reply, env, state}
{:ok, d}
end
def handle_call({:yank, local}, _from, {d, _} = state) do
def handle_call({:yank, local}, _from, d) do
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_edges(d, :digraph.out_edges(d, local))
{:reply, {[], out_vertices}, state}
{:reply, {[], out_vertices}, d}
end
def handle_call(:digraph, _from, {d, _} = state) do
{:reply, d, state}
def handle_call(:digraph, _from, d) do
{:reply, d, d}
end
@doc false
def handle_info(_msg, state) do
{:noreply, state}
def handle_info(_msg, d) do
{:noreply, d}
end
def handle_cast({:add_local, from, to}, {d, _} = state) do
def handle_cast({:add_local, from, to}, d) do
handle_add_local(d, from, to)
{:noreply, state}
{:noreply, d}
end
def handle_cast({:add_import, function, module, {name, arity}}, {d, _} = state) do
def handle_cast({:add_import, function, module, {name, arity}}, d) do
handle_import(d, function, module, name, arity)
{:noreply, state}
{:noreply, d}
end
def handle_cast({:add_definition, kind, tuple}, {d, _} = state) do
def handle_cast({:add_definition, kind, tuple}, d) do
handle_add_definition(d, kind, tuple)
{:noreply, state}
{:noreply, d}
end
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, {d, _} = state) do
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, d) do
for i <- :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, {name, i})
handle_add_local(d, {name, i}, {name, arity})
end
{:noreply, state}
{:noreply, d}
end
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, d) do
for from <- in_neigh do
:digraph.add_vertex(d, from)
replace_edge!(d, from, tuple)
@@ -299,11 +273,11 @@ defmodule Module.LocalsTracker do
replace_edge!(d, tuple, to)
end
{:noreply, state}
{:noreply, d}
end
def handle_cast(:stop, state) do
{:stop, :normal, state}
def handle_cast(:stop, d) do
{:stop, :normal, d}
end
@doc false
@@ -1,14 +1,15 @@
%% Module responsible for tracking invocations of module calls.
-module(elixir_locals).
-export([
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
setup/1, cleanup/1, cache_env/1, cache_env/2, get_cached_env/1,
record_local/2, record_local/3, record_import/4,
record_definition/3, record_defaults/4,
ensure_no_import_conflict/3, warn_unused_local/3, format_error/1
]).
-include("elixir.hrl").
-define(attr, {elixir, locals_tracker}).
-define(cache, {elixir, cache_env}).
-define(tracker, 'Elixir.Module.LocalsTracker').
setup(Module) ->
@@ -56,18 +57,29 @@ if_tracker(Module, Default, Callback) ->
%% CACHING
cache_env(#{module := Module, line := Line} = E) ->
try ets:lookup_element(elixir_module:data_table(Module), ?attr, 2) of
Pid ->
{Pid, {Line, ?tracker:cache_env(Pid, E#{line := nil, vars := []})}}
catch
error:badarg ->
{Escaped, _} = elixir_quote:escape(E#{vars := []}, false),
Escaped
end.
get_cached_env({Pid, {Line, Ref}}) -> (?tracker:get_cached_env(Pid, Ref))#{line := Line};
get_cached_env(Env) -> Env.
cache_env(#{module := Module} = E) ->
cache_env(elixir_module:data_table(Module), E).
cache_env(Table, #{line := Line} = E) ->
Cache = E#{line := nil, vars := []},
Pos =
case ets:lookup(Table, ?cache) of
[{_, Key, Cache}] ->
Key;
[{_, PrevKey, _}] ->
Key = PrevKey + 1,
ets:insert(Table, {{cache_env, Key}, Cache}),
ets:insert(Table, {?cache, Key, Cache}),
Key
end,
{Table, {Line, Pos}}.
get_cached_env({Table, {Line, Pos}}) ->
(ets:lookup_element(Table, {cache_env, Pos}, 2))#{line := Line};
get_cached_env(Env) ->
Env.
%% ERROR HANDLING
@@ -210,7 +210,8 @@ build(Line, File, Module, Lexical) ->
{typep, [], true, nil},
% Internal
{{elixir, impls}, []}
{{elixir, impls}, []},
{{elixir, cache_env}, 0, nil}
]),
Persisted = [behaviour, on_load, compile, external_resource, dialyzer, vsn],

1 comment on commit 8518652

@michalmuskala

This comment has been minimized.

Show comment
Hide comment
@michalmuskala

michalmuskala Jun 29, 2017

Member

Nice! 10-15% overall speedup is going to be noticeable

Member

michalmuskala commented on 8518652 Jun 29, 2017

Nice! 10-15% overall speedup is going to be noticeable

Please sign in to comment.