What are some strategies to make Dialyzer run faster?

We have a rather large code base ~2200 elixir files and growing. On my nice Ryzen 9, it takes ~47 seconds to analyze the generated PLT files. Our config looks like this:

  defp dialyzer(env) do
    [
      plt_add_apps: [:mix, :ex_unit],
      flags: ~w(
        error_handling
        extra_return
        missing_return
      )a,
      plt_file: {:no_warn, "priv/plts/dialyzer-#{env}.plt"},
      list_unused_filters: true
    ]
  end

In our CI environment, dialyzer is taking ~2 minutes. I blame the underlying server equipment here, but locally this is painfully slow to run and to build the PLTs take even longer than our test suite does to run. (14,000+ tests ~100 seconds).

What are some strategies that others have employed to speed this up or help the dialyzer along? One mentioned was to avoid opaque types.

2 Likes

You can build just the PLTs without doing analysis via dialyzer --plt. I work this into my cache system (varies by CI provider) with a cache key based on elixir + erlang versions and a hash of mix.lock; so PLTs only build when any of those change. This amortizes your PLT build times over just commits that would require rebuilding PLTs by busting the cache.

Generally,

  1. Try to restore PLT cache based on language/library versions
  2. Build the PLTs on cache miss
  3. Run dialyzer normally using cached/newly built PLTs

Steps 1 and 3 are fast and the happy path. In practice this looks like (for Github-flavored CI):

- name: Restore mix typecheck cache
  id: mix-typecheck-cache
  uses: actions/cache@v4
  with:
    path: priv/plts
    key: plts-otp-${{ steps.beam-versions.outputs.otp-version }}-elixir-${{ steps.beam-versions.outputs.elixir-version }}-mix-lock-file-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Setup typechecking
  if: steps.mix-typecheck-cache.outputs.cache-hit != 'true'
  run: mix dialyzer --plt --format dialyxir

- name: Run typecheck tasks
  run: mix dialyzer --format dialyxir
2 Likes

Yea we definitely cache the PLTs prior, but the cache is busted if mix lock is changed.

1 Like

Ah i see. I have no experience trying to accelerate the generation of PLTs beyond caching, as that’s always been sufficient for the projects I’ve worked on. Does your mix.lock change often enough for this to remain a problem day-to-day?

Unfortunately we have ~181 dependencies and we do bump deps frequently. I should also mention that the caching also is calculated based on tool-version, all the config files (we have conditional compilation yay), and this mix lock.

1 Like