Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler: enable parallel codegen with MT #14748

Merged

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Jun 25, 2024

Implements parallel codegen of object files when MT is enabled in the compiler (-Dpreview_mt).

It only impacts codegen for compilations with more than one compilation unit (module), that is when neither of --single-module, --release or --cross-compile is specified. This behavior is identical to the fork based codegen.

Advantages:

  • allows parallel codegen on Windows (untested);
  • no need to fork many processes;
  • no repeated GC collections in each forked processes;
  • a simple Channel to distribute work efficiently (no need for IPC).

The main points are increased portability and simpler logic, despite having to take care of LLVM thread safety quirks (see comments).

Issues:

  1. The threads arg actually depicts the number of fibers, not threads, which is confusing and problematic: increasing threads but not CRYSTAL_WORKERS will lead to more fibers than threads, with fibers being sheduled on the same threads, which won't bring any improvement.

    In fact CRYSTAL_WORKERS defaults to 4, when threads defaulted to 8. With this patch it defaults to CRYSTAL_WORKERS, so MT can end up being slower if we don't specify CRYSTAL_WORKERS=8.

  2. This is still not as efficient as it could be. The main fiber (that feeds the worker fibers) can get blocked by a worker fiber doing codegen, leading the other workers to starve. This is easily noticeable when compiling with -O1 for example.

Both issues will be fixable with RFC 2 where we can start an explicit context to run the worker fibers or start N isolated contexts (maybe a better idea). Until then, one should increase CRYSTAL_WORKERS.

Supersedes #14227 and doesn't segfault (so far) with LLVM 12 or LLVM 18.1 🤞

TODO:

  • wait for Compiler: refactor codegen #14760
  • cleanup
  • rename the method as mt_parallel(units, n_threads)
  • figure out thread safety of LLVM legacy pass manager (it's thread unsafe 💥)
  • consider increasing the channel size (until we can use ExecutionContext)
  • consider a CRYSTAL_CONFIG_WORKERS to configure the default number of workers at compile time instead of the hardcoded 4 (in a distinct PR)

@straight-shoota
Copy link
Member

This looks great. But it also seems to be a mix of different changes. Could we extract the independent refactorings (such as extracting sequential_codegen and fork_codegen, memoization of some methods) to their own PRs?

@ysbaddaden ysbaddaden marked this pull request as draft June 28, 2024 12:49
@ysbaddaden ysbaddaden force-pushed the feature/compiler-mt-codegen-2 branch from 3a08d9e to ac91f7c Compare July 2, 2024 11:26
@ysbaddaden
Copy link
Contributor Author

Rebased on top of #14760.

src/compiler/crystal/compiler.cr Outdated Show resolved Hide resolved
src/compiler/crystal/compiler.cr Show resolved Hide resolved
src/compiler/crystal/compiler.cr Outdated Show resolved Hide resolved
src/compiler/crystal/compiler.cr Outdated Show resolved Hide resolved
straight-shoota pushed a commit that referenced this pull request Aug 6, 2024
Refactors `Crystal::Compiler`:

1. extracts `#sequential_codegen`, `#parallel_codegen` and `#fork_codegen` methods;
2. merges `#codegen_many_units` into `#codegen` directly;
3. stops collecting reused units: `#fork_codegen` now updates `CompilationUnit#reused_compilation_unit?` state as reported by the forked processes, and `#print_codegen_stats` now counts & filters the reused units.

Prerequisite for #14748 that will introduce `#mt_codegen`.
When compiled with -Dpreview_mt the compiler will take advantage of the
MT environment to codegen the compilation units in parallel, avoiding
fork (that's not supported with MT) and allowing parallel codegen on
Windows.
@ysbaddaden
Copy link
Contributor Author

Rebased from master that merged #14760 (prerequisite) and ready for review.

@ysbaddaden ysbaddaden marked this pull request as ready for review September 3, 2024 09:28
src/compiler/crystal/compiler.cr Outdated Show resolved Hide resolved
@beta-ziliani
Copy link
Member

consider a CRYSTAL_CONFIG_WORKERS to configure the default number of workers at compile time instead of the hardcoded 4 (in a distinct PR)

Isn't this addressed (using CRYSTAL_WORKERS)? Or is it something different?

@ysbaddaden
Copy link
Contributor Author

@beta-ziliani this could be a compile time ENV to change the default number of threads/schedulers. It's tangential to this pull request.

@straight-shoota straight-shoota added this to the 1.14.0 milestone Sep 19, 2024
@Fryguy
Copy link
Contributor

Fryguy commented Sep 19, 2024

The RFC-2 link in the OP here points to a non-existing URL. Think it was supposed to be crystal-lang/rfcs#2?

@straight-shoota straight-shoota merged commit c74f6bc into crystal-lang:master Sep 21, 2024
65 checks passed
@ysbaddaden ysbaddaden deleted the feature/compiler-mt-codegen-2 branch September 22, 2024 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants