Skip to content

Commit 2a19411

Browse files
author
José + Eric
committed
Cache the environment for function definition
This speeds up compilation and reduces memory consumption. For example, Ecto test suite consumes consistently 30% less memory and loads 20% faster.
1 parent 5e2ea08 commit 2a19411

File tree

5 files changed

+86
-39
lines changed

5 files changed

+86
-39
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,16 +2982,18 @@ defmodule Kernel do
29822982
defp define(kind, call, expr, env) do
29832983
assert_module_scope(env, kind, 2)
29842984
assert_no_function_scope(env, kind, 2)
2985+
line = env_line(env)
29852986

29862987
{ call, uc } = :elixir_quote.escape(call, true)
29872988
{ expr, ue } = :elixir_quote.escape(expr, true)
29882989

29892990
# Do not check clauses if any expression was unquoted
29902991
check_clauses = not(ue or uc)
2992+
pos = :elixir_env.cache(env)
29912993

29922994
quote do
2993-
:elixir_def.store_definition(unquote(kind), unquote(check_clauses),
2994-
unquote(call), unquote(expr), __ENV__)
2995+
:elixir_def.store_definition(unquote(line), unquote(kind), unquote(check_clauses),
2996+
unquote(call), unquote(expr), unquote(pos))
29952997
end
29962998
end
29972999

@@ -3784,6 +3786,7 @@ defmodule Kernel do
37843786
end
37853787

37863788
defp env_module(env), do: :erlang.element(2, env)
3789+
defp env_line(env), do: :erlang.element(4, env)
37873790
defp env_function(env), do: :erlang.element(5, env)
37883791
defp env_context(env), do: :erlang.element(6, env)
37893792
defp env_vars(env), do: :erlang.element(13, env)

lib/elixir/lib/kernel/lexical_tracker.ex

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# any of the `GenServer.Behaviour` conveniences.
99
defmodule Kernel.LexicalTracker do
1010
@timeout 30_000
11-
@behavior :gen_server
11+
@behaviour :gen_server
1212

1313
@import 2
1414
@alias 3
@@ -83,6 +83,16 @@ defmodule Kernel.LexicalTracker do
8383
unused(pid, @alias)
8484
end
8585

86+
@doc false
87+
def cache(pid, env) do
88+
:gen_server.call(pid, { :cache, env }, @timeout)
89+
end
90+
91+
@doc false
92+
def get_cached(pid, ref) do
93+
:gen_server.call(pid, { :get_cached, ref }, @timeout)
94+
end
95+
8696
defp unused(pid, pos) do
8797
ets = :gen_server.call(pid, :ets, @timeout)
8898
:ets.foldl(fn
@@ -97,60 +107,75 @@ defmodule Kernel.LexicalTracker do
97107

98108

99109
def init([]) do
100-
{ :ok, :ets.new(:lexical, [:protected]) }
110+
{ :ok, { :ets.new(:lexical, [:protected]), [] } }
111+
end
112+
113+
def handle_call({ :cache, env }, _from, { d, cache }) do
114+
case cache do
115+
[{i,^env}|_] ->
116+
{ :reply, i, { d, cache } }
117+
t ->
118+
i = length(t)
119+
{ :reply, i, { d, [{i,env}|t] } }
120+
end
121+
end
122+
123+
def handle_call({ :get_cached, ref }, _from, { _, cache } = state) do
124+
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
125+
{ :reply, env, state }
101126
end
102127

103-
def handle_call(:ets, _from, d) do
104-
{ :reply, d, d }
128+
def handle_call(:ets, _from, { d, _ } = state) do
129+
{ :reply, d, state }
105130
end
106131

107-
def handle_call(_request, _from, d) do
108-
{ :noreply, d }
132+
def handle_call(_request, _from, state) do
133+
{ :noreply, state }
109134
end
110135

111-
def handle_cast({ :remote_dispatch, module }, d) do
136+
def handle_cast({ :remote_dispatch, module }, { d, _ } = state) do
112137
add_module(d, module)
113-
{ :noreply, d }
138+
{ :noreply, state }
114139
end
115140

116-
def handle_cast({ :import_dispatch, module }, d) do
141+
def handle_cast({ :import_dispatch, module }, { d, _ } = state) do
117142
add_dispatch(d, module, @import)
118-
{ :noreply, d }
143+
{ :noreply, state }
119144
end
120145

121-
def handle_cast({ :alias_dispatch, module }, d) do
146+
def handle_cast({ :alias_dispatch, module }, { d, _ } = state) do
122147
add_dispatch(d, module, @alias)
123-
{ :noreply, d }
148+
{ :noreply, state }
124149
end
125150

126-
def handle_cast({ :add_import, module, line, warn }, d) do
151+
def handle_cast({ :add_import, module, line, warn }, { d, _ } = state) do
127152
add_directive(d, module, line, warn, @import)
128-
{ :noreply, d }
153+
{ :noreply, state }
129154
end
130155

131-
def handle_cast({ :add_alias, module, line, warn }, d) do
156+
def handle_cast({ :add_alias, module, line, warn }, { d, _ } = state) do
132157
add_directive(d, module, line, warn, @alias)
133-
{ :noreply, d }
158+
{ :noreply, state }
134159
end
135160

136-
def handle_cast(:stop, d) do
137-
{ :stop, :normal, d }
161+
def handle_cast(:stop, state) do
162+
{ :stop, :normal, state }
138163
end
139164

140-
def handle_cast(_msg, d) do
141-
{ :noreply, d }
165+
def handle_cast(_msg, state) do
166+
{ :noreply, state }
142167
end
143168

144-
def handle_info(_msg, d) do
145-
{ :noreply, d }
169+
def handle_info(_msg, state) do
170+
{ :noreply, state }
146171
end
147172

148-
def terminate(_reason, _d) do
173+
def terminate(_reason, _state) do
149174
:ok
150175
end
151176

152-
def code_change(_old, d, _extra) do
153-
{ :ok, d }
177+
def code_change(_old, state, _extra) do
178+
{ :ok, state }
154179
end
155180

156181
# Callbacks helpers

lib/elixir/src/elixir_bootstrap.erl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
1212

1313
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
14-
'MACRO-def'(Caller, Call, Expr) -> definition(Caller, def, Call, Expr).
15-
'MACRO-defp'(Caller, Call, Expr) -> definition(Caller, defp, Call, Expr).
14+
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
15+
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
1616

1717
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
18-
'MACRO-defmacro'(Caller, Call, Expr) -> definition(Caller, defmacro, Call, Expr).
19-
'MACRO-defmacrop'(Caller, Call, Expr) -> definition(Caller, defmacrop, Call, Expr).
18+
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
19+
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
2020

2121
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
2222
{ Escaped, _ } = elixir_quote:escape(Block, false),
@@ -35,10 +35,10 @@
3535
{defmodule,2},
3636
{defp,2}].
3737

38-
definition(_Caller, Kind, Call, Expr) ->
38+
define({Line,E}, Kind, Call, Expr) ->
3939
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
4040
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
41-
Args = [Kind, not(UC or UE), EscapedCall, EscapedExpr, env()],
41+
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_env:cache(E)],
4242
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
4343

4444
unless_loaded(Fun, Args, Callback) ->

lib/elixir/src/elixir_def.erl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-module(elixir_def).
33
-export([table/1, clauses_table/1, setup/1,
44
cleanup/1, reset_last/1, lookup_definition/2,
5-
delete_definition/2, store_definition/5, unwrap_definitions/2,
5+
delete_definition/2, store_definition/6, unwrap_definitions/2,
66
store_each/8, format_error/1]).
77
-include("elixir.hrl").
88

@@ -48,8 +48,8 @@ delete_definition(Module, Tuple) ->
4848
% Invoked by the wrap definition with the function abstract tree.
4949
% Each function is then added to the function table.
5050

51-
store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
52-
#elixir_env{line=Line} = E = elixir_env:ex_to_env(ExEnv),
51+
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
52+
E = (elixir_env:get_cached(Pos))#elixir_env{line=Line},
5353
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
5454

5555
{ Name, Args } = case NameAndArgs of
@@ -76,13 +76,13 @@ store_definition(Kind, CheckClauses, Call, Body, ExEnv) ->
7676
LinifyBody = elixir_quote:linify(Line, Key, Body),
7777

7878
assert_no_aliases_name(Line, Name, Args, E),
79-
store_definition(Kind, Line, DoCheckClauses, Name,
79+
store_definition(Line, Kind, DoCheckClauses, Name,
8080
LinifyArgs, LinifyGuards, LinifyBody, File, E).
8181

82-
store_definition(Kind, Line, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
82+
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
8383
Arity = length(Args),
8484
Tuple = { Name, Arity },
85-
E = ER#elixir_env{function=Tuple,vars=[]},
85+
E = ER#elixir_env{function=Tuple},
8686
elixir_locals:record_definition(Tuple, Kind, Module),
8787

8888
Location = retrieve_file(Line, MetaFile, Module, E),

lib/elixir/src/elixir_env.erl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
-module(elixir_env).
22
-include("elixir.hrl").
33
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
4+
-export([cache/1, get_cached/1]).
45
-export([mergea/2, mergev/2]).
6+
-define(tracker, 'Elixir.Kernel.LexicalTracker').
57

68
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
79

@@ -36,3 +38,20 @@ mergev(E1, E2) ->
3638

3739
mergea(E1, E2) ->
3840
E2#elixir_env{vars=E1#elixir_env.vars}.
41+
42+
%% Caching
43+
44+
cache(#elixir_env{} = RE) ->
45+
E = RE#elixir_env{line=nil,vars=[]},
46+
case E#elixir_env.lexical_tracker of
47+
nil -> escape(E);
48+
Pid -> { Pid, ?tracker:cache(Pid, E) }
49+
end;
50+
cache(ExEnv) ->
51+
cache(ex_to_env(ExEnv)).
52+
53+
get_cached({Pid,Ref}) -> ?tracker:get_cached(Pid, Ref);
54+
get_cached(Env) -> Env.
55+
56+
escape(E) ->
57+
{ Escaped, _ } = elixir_quote:escape(E, false), Escaped.

0 commit comments

Comments
 (0)