Skip to content

Inefficient CPU utilization when compiling dependencies #14200

@liamwhite

Description

Elixir and Erlang/OTP versions

Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [jit:ns]

Elixir 1.18.1 (compiled with Erlang/OTP 27)

Operating system

Linux

Current behavior

I have an application which has a lot of its own files and a fair number of dependencies. When running mix deps.compile for the first time (so no _build directory exists at this point), mix spends a lot of time compiling applications, one after the other, in a serial fashion.

The problem with this approach is that I have a 16-core, 32-logical-core processor, and System.schedulers_online() returns 32, so mix should really always be trying to schedule 32 compilation units between applications with satisfied dependencies, which it does not appear to be doing.

Since most dependency applications are just a handful of files, this results in just a few cores being used most of the time. The logging and utilization suggests that all of the files within each application get compiled in parallel; then the result of all compilations is awaited before generating the next application.

An example of a deps list in a new mix project that doesn't make the best use of the CPU during compilation follows:

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:phoenix, "~> 1.7"},
      {:phoenix_pubsub, "~> 2.1"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.9"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.3"},
      {:phoenix_view, "~> 2.0"},
      {:phoenix_live_reload, "~> 1.4", only: :dev},
      {:gettext, "~> 0.22"},
      {:jason, "~> 1.4"},
      {:bandit, "~> 1.2"},
      {:phoenix_pubsub_redis, "~> 3.0"},
      {:ecto_network, "~> 1.3"},
      {:bcrypt_elixir, "~> 3.0"},
      {:pot, "~> 1.0"},
      {:secure_compare, "~> 0.1"},
      {:nimble_parsec, "~> 1.2"},
      {:qrcode, "~> 0.1"},
      {:redix, "~> 1.2"},
      {:remote_ip, "~> 1.1"},
      {:briefly, "~> 0.4"},
      {:req, "~> 0.5"},
      {:exq, "~> 0.17"},
      {:ex_aws, "~> 2.0"},
      {:ex_aws_s3, "~> 2.0"},
      {:sweet_xml, "~> 0.7"},
      {:inet_cidr, "~> 1.0"},
      {:swoosh, "~> 1.17"},
      {:mua, "~> 0.2.0"},
      {:mail, "~> 0.3.0"},
      {:ex_doc, "~> 0.30"},
      {:sobelow, "~> 0.11"},
      {:mix_audit, "~> 2.1"},
      {:dialyxir, "~> 1.2"}
    ]
  end

Example

$ time mix deps.compile
[...]

real	0m30.369s
user	1m56.178s
sys	0m30.771s

-> 3.83x speedup over serial compilation

Expected behavior

My application contains 640+ elixir compilation units. After cleaning and running mix compile, mix will max out the CPU immediately and consistently schedule work for all 32 logical cores until there are no new compilation tasks to complete.

Example:

$ time mix compile
[...]
Generated philomena app

real	0m13.939s
user	1m40.286s
sys	0m22.636s

-> 7.19x speedup over serial compilation

This would also be the desired behavior when compiling application dependencies.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions