Skip to content

Commit 3e18aaf

Browse files
committed
Measure and optimize writing of .beam files in the compiler
This should provide meaningful benefits when working with containers and network mounted drives.
1 parent bcb73a4 commit 3e18aaf

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-16
lines changed

lib/elixir/lib/file.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -706,15 +706,15 @@ defmodule File do
706706
path = IO.chardata_to_string(path)
707707

708708
with {:error, :enoent} <- :elixir_utils.change_universal_time(path, time),
709-
:ok <- write(path, "", [:append]),
709+
:ok <- write(path, "", [:raw, :append]),
710710
do: :elixir_utils.change_universal_time(path, time)
711711
end
712712

713713
def touch(path, time) when is_integer(time) do
714714
path = IO.chardata_to_string(path)
715715

716716
with {:error, :enoent} <- :elixir_utils.change_posix_time(path, time),
717-
:ok <- write(path, "", [:append]),
717+
:ok <- write(path, "", [:raw, :append]),
718718
do: :elixir_utils.change_posix_time(path, time)
719719
end
720720

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -380,27 +380,73 @@ defmodule Kernel.ParallelCompiler do
380380
end
381381
end
382382

383-
defp write_module_binaries(result, {:compile, path}, timestamp) do
384-
File.mkdir_p!(path)
385-
Code.prepend_path(path)
386-
387-
for {{:module, module}, {binary, _}} when is_binary(binary) <- result do
388-
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
389-
File.write!(full_path, binary)
390-
if timestamp, do: File.touch!(full_path, timestamp)
391-
module
392-
end
383+
defp write_module_binaries(result, {:compile, path}, state) when map_size(result) > 0 do
384+
profile(state, "writing modules to disk", fn ->
385+
File.mkdir_p!(path)
386+
Code.prepend_path(path)
387+
timestamp = state.beam_timestamp
388+
389+
# We fan-out the writes as that improves performance
390+
# when writing hundreds of beam files. This is cheap as
391+
# we only transfer atoms and binaries across processes.
392+
pool_size = min(map_size(result), state.schedulers)
393+
394+
pool_list =
395+
for _ <- 1..pool_size do
396+
spawn_link(fn -> write_loop(path, timestamp) end)
397+
end
398+
399+
pool_tuple = List.to_tuple(pool_list)
400+
401+
{modules, _} =
402+
Enum.flat_map_reduce(result, 0, fn
403+
{{:module, module}, {binary, _}}, scheduler when is_binary(binary) ->
404+
send(elem(pool_tuple, scheduler), {:write, module, binary})
405+
{[module], rem(scheduler + 1, pool_size)}
406+
407+
_, scheduler ->
408+
{[], scheduler}
409+
end)
410+
411+
pool_refs =
412+
for pid <- pool_list do
413+
ref = Process.monitor(pid)
414+
send(pid, :done)
415+
ref
416+
end
417+
418+
for ref <- pool_refs do
419+
receive do
420+
{:DOWN, ^ref, _, _, _} -> :ok
421+
end
422+
end
423+
424+
modules
425+
end)
393426
end
394427

395-
defp write_module_binaries(result, _output, _timestamp) do
428+
defp write_module_binaries(result, _output, _state) do
396429
for {{:module, module}, {binary, _}} when is_binary(binary) <- result, do: module
397430
end
398431

432+
defp write_loop(path, timestamp) do
433+
receive do
434+
{:write, module, binary} ->
435+
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
436+
File.write!(full_path, binary, [:raw])
437+
if timestamp, do: File.touch!(full_path, timestamp)
438+
write_loop(path, timestamp)
439+
440+
:done ->
441+
:ok
442+
end
443+
end
444+
399445
## Verification
400446

401447
defp verify_modules(result, compile_warnings, dependent_modules, state) do
402-
modules = write_module_binaries(result, state.output, state.beam_timestamp)
403-
_ = state.after_compile.()
448+
modules = write_module_binaries(result, state.output, state)
449+
profile(state, "after compile callback", state.after_compile)
404450

405451
runtime_warnings =
406452
if state.verification? do

lib/elixir/src/elixir_utils.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ change_posix_time(Name, Time) when is_integer(Time) ->
127127
change_universal_time(Name, {{Y, M, D}, {H, Min, Sec}}=Time)
128128
when is_integer(Y), is_integer(M), is_integer(D),
129129
is_integer(H), is_integer(Min), is_integer(Sec) ->
130-
file:write_file_info(Name, #file_info{mtime=Time}, [{time, universal}]).
130+
file:write_file_info(Name, #file_info{mtime=Time}, [raw, {time, universal}]).
131131

132132
relative_to_cwd(Path) ->
133133
try elixir_config:get(relative_paths) of

0 commit comments

Comments
 (0)