SerenityOS / jakt

The Jakt Programming Language
BSD 2-Clause "Simplified" License
2.82k stars 240 forks source link

Compile time performance roadmap #1039

Open ilyapopov opened 2 years ago

ilyapopov commented 2 years ago

Jakt compiler is quite slow to compile. This slows down development of the language. Most of the time is spent in the Clang.

Currently on my machine:

$ time ./build/jakt selfhost/main.jakt > build/baseline.cpp
real    0m4.788s
$ time clang++ -std=c++20 -I runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literals build/baseline.cpp
real    0m14.547s

Clang part

I have profiled this a little and saw some directions how compilation can be sped up:

Jakt part

I have not investigated that yet

Appendix

Profiling was done with clang with command line:

time clang++ -std=c++20 -I runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literals -ftime-trace -c -o build/baseline.o build/baseline.cpp

This generates baseline.json in the build directory.

Profile is attached: baseline.zip you can inspect it using Speedscope, Perfetto, Chrome dev tools, etc.

alimpfard commented 2 years ago

Replacing Variant with tagged unions improves the time by ~20-30%, see experiment here, a large part of the remaining template instantiation time is spent resolving the conversion of runtime-known indices to types and v./v. for is Variant.

Hendiadyoin1 commented 2 years ago

Point two will be fixed by #1038 If you link that in the bullet-point it will auto check it when the PR is merged

maddanio commented 2 years ago

The index lookup can be done in the compiler instead of using Variant::has, that should speed things up a lot. note that the codegen is a bit confused as it tries to handle the case where the lhs of the is operator is not an enum, but that is never the case at this point in the code

ilyapopov commented 2 years ago

Turns out, -fno-exceptions cuts about 2.7s (23%) from build time!

hyperfine -r 5 "cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literalsi jakt.cpp" "cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literals -fno-exceptions jakt.cpp"
Benchmark 1: cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literalsi jakt.cpp
  Time (mean ± σ):     14.772 s ±  0.092 s    [User: 14.208 s, System: 0.552 s]
  Range (min … max):   14.666 s … 14.881 s    5 runs

Benchmark 2: cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literals -fno-exceptions jakt.cpp
  Time (mean ± σ):     11.989 s ±  0.069 s    [User: 11.431 s, System: 0.541 s]
  Range (min … max):   11.874 s … 12.059 s    5 runs

Summary
  'cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literals -fno-exceptions jakt.cpp' ran
    1.23 ± 0.01 times faster than 'cd jakt/build/ && clang++ -std=c++20 -I ../runtime/ -Wno-parentheses-equality -Wno-trigraphs -Wno-user-defined-literalsi jakt.cpp'

Just have to make sure this does not break anything.

maddanio commented 2 years ago

well, question is to we ever want to handle any c++ exceptions in jakt? i.e. in c++ extern blocks? if not then it is pretty sure ok

Hendiadyoin1 commented 2 years ago

We don't do c++ exceptions here We have our own Error for that, which is supposed to be cheaper

awesomekling commented 2 years ago

Excellent initiative! For the Jakt part, make sure you build jakt with optimizations (jakt -O). It should run significantly faster than 0m4.788s :^)

ilyapopov commented 2 years ago

I have also analyzed the build using excellent ClangBuildAnalyzer

For the current master, the report looks like:

View report ``` Analyzing build trace from '1'... **** Time summary: Compilation (2 times): Parsing (frontend): 7.4 s Codegen & opts (backend): 4.3 s **** Files that took longest to parse (compiler frontend): 5948 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o 1438 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o **** Files that took longest to codegen (compiler backend): 4258 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o **** Templates that took longest to instantiate: 131 ms: Jakt::Variant::can_contain<$> (305 times, avg 3 ms) 927 ms: Jakt::Variant<$>::index_of<$> (293 times, avg 3 ms) 884 ms: Jakt::Detail::index_of<$> (283 times, avg 3 ms) 621 ms: JaktInternal::ExplicitValueOrControlFlow<$>::ExplicitValueOrControlF... (315 times, avg 1 ms) 373 ms: Jakt::Variant<$> (349 times, avg 1 ms) 366 ms: Jakt::Detail::VariantConstructors<$>::VariantConstructors (424 times, avg 0 ms) 316 ms: JaktInternal::Array<$>::create_with (53 times, avg 5 ms) 272 ms: Jakt::Variant<$>::visit<$> (39 times, avg 6 ms) 249 ms: Jakt::ErrorOr<$> (222 times, avg 1 ms) 227 ms: JaktInternal::Array<$>::create_empty (56 times, avg 4 ms) 226 ms: Jakt::Detail::VisitImpl<$>::visit<$> (39 times, avg 5 ms) 213 ms: Jakt::Detail::VisitImpl<$>::visit_impl<$> (27 times, avg 7 ms) 149 ms: JaktInternal::ExplicitValueOrControlFlow<$> (105 times, avg 1 ms) 139 ms: Jakt::adopt_nonnull_ref_or_enomem<$> (78 times, avg 1 ms) 126 ms: Jakt::StringBuilder::appendff<$> (87 times, avg 1 ms) 122 ms: Jakt::Variant<$>::Variant (42 times, avg 2 ms) 114 ms: Jakt::VariadicFormatParams<$>::VariadicFormatParams (88 times, avg 1 ms) 97 ms: Jakt::Variant<$>::set<$> (29 times, avg 3 ms) 96 ms: JaktInternal::Dictionary<$>::create_with_entries (12 times, avg 8 ms) 94 ms: Jakt::Formatter<$>::format (67 times, avg 1 ms) 91 ms: Jakt::__format_value<$> (69 times, avg 1 ms) 83 ms: Jakt::Detail::VariantConstructors<$> (47 times, avg 1 ms) 68 ms: JaktInternal::Dictionary<$>::set (12 times, avg 5 ms) 67 ms: Jakt::HashMap<$>::set (12 times, avg 5 ms) 66 ms: Jakt::HashTable<$>::try_set<$> (13 times, avg 5 ms) 61 ms: Jakt::Detail::Variant<$>::move_ (18 times, avg 3 ms) 56 ms: JaktInternal::Dictionary<$>::create_empty (12 times, avg 4 ms) 54 ms: JaktInternal::ExplicitValueOrControlFlow<$>::release_return (41 times, avg 1 ms) 52 ms: Jakt::HashTable<$>::try_rehash (13 times, avg 4 ms) 49 ms: Jakt::Variant<$>::~Variant (18 times, avg 2 ms) **** Functions that took longest to compile: 24 ms: Jakt::typechecker::Typechecker::typecheck_binary_operation(Jakt::Non... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 13 ms: Jakt::typechecker::Typechecker::typecheck_expression(Jakt::NonnullRe... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 12 ms: Jakt::codegen::CodeGenerator::codegen_expression(Jakt::NonnullRefPtr... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 11 ms: Jakt::main(JaktInternal::Array) (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 9 ms: Jakt::typechecker::Typechecker::typecheck_enum(Jakt::parser::ParsedR... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 9 ms: Jakt::typechecker::Typechecker::typecheck_typename(Jakt::NonnullRefP... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 8 ms: Jakt::typechecker::Typechecker::typecheck_call(Jakt::parser::ParsedC... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 8 ms: Jakt::ide::get_type_signature(Jakt::NonnullRefPtr, voi... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 3 ms: Jakt::typechecker::CheckedFunction::debug_description() const (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) 3 ms: Jakt::typechecker::Typechecker::substitute_typevars_in_type_helper(J... (/home/ilya/devel/external/jakt/build/jakt_stage1_main.cpp) **** Function sets that took longest to compile / optimize: 45 ms: Jakt::Formatter<$>::format(Jakt::FormatBuilder&, JaktInternal::Array... (50 times, avg 0 ms) 24 ms: Jakt::typechecker::Typechecker::typecheck_binary_operation(Jakt::Non... (1 times, avg 24 ms) 13 ms: Jakt::typechecker::Typechecker::typecheck_expression(Jakt::NonnullRe... (1 times, avg 13 ms) 12 ms: Jakt::codegen::CodeGenerator::codegen_expression(Jakt::NonnullRefPtr... (1 times, avg 12 ms) 11 ms: Jakt::main(JaktInternal::Array<$>) (1 times, avg 11 ms) 9 ms: Jakt::Formatter<$>::format(Jakt::FormatBuilder&, JaktInternal::Dicti... (8 times, avg 1 ms) 9 ms: Jakt::typechecker::Typechecker::typecheck_typename(Jakt::NonnullRefP... (1 times, avg 9 ms) 8 ms: Jakt::typechecker::Typechecker::typecheck_call(Jakt::parser::ParsedC... (1 times, avg 8 ms) 8 ms: Jakt::ide::get_type_signature(Jakt::NonnullRefPtr::~Formatter() (1 times, avg 3 ms) 3 ms: Jakt::typechecker::Typechecker::substitute_typevars_in_type_helper(J... (1 times, avg 3 ms) 3 ms: Jakt::parser::parsed_expression_equals(Jakt::NonnullRefPtr::format(Jakt::FormatBuilder&, Jakt::Tuple<$> cons... (5 times, avg 0 ms) 3 ms: Jakt::typechecker::Typechecker::typecheck_for(Jakt::String, Jakt::ut... (1 times, avg 3 ms) 3 ms: Jakt::typechecker::Typechecker::typecheck_statement(Jakt::NonnullRef... (1 times, avg 3 ms) 3 ms: Jakt::typechecker::Typechecker::typecheck_array(Jakt::typechecker::S... (1 times, avg 3 ms) 3 ms: Jakt::typechecker::Typechecker::typecheck_dictionary(JaktInternal::A... (1 times, avg 3 ms) 3 ms: Jakt::typechecker::Typechecker::typecheck_match(Jakt::NonnullRefPtr<... (1 times, avg 3 ms) *** Expensive headers: 175 ms: /home/ilya/devel/external/jakt/runtime/lib.h (included 1 times, avg 175 ms), included via: jakt_stage1_main.cpp.o (175 ms) done in 0.0s. ```

A lot of time is taken by visit. After #1038, the report looks like

View report ``` ../ClangBuildAnalyzer/build/ClangBuildAnalyzer --analyze 1 Analyzing build trace from '1'... **** Time summary: Compilation (2 times): Parsing (frontend): 7.0 s Codegen & opts (backend): 6.4 s **** Files that took longest to parse (compiler frontend): 5613 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o 1412 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o **** Files that took longest to codegen (compiler backend): 6376 ms: build/CMakeFiles/jakt_stage1.dir//jakt_stage1_main.cpp.o **** Templates that took longest to instantiate: 51 ms: Jakt::Variant, JaktInternal::Expli... (1 times, avg 42 ms) 28 ms: Jakt::Detail::Variant::can_contain<$> (302 times, avg 3 ms) 910 ms: Jakt::Variant<$>::index_of<$> (296 times, avg 3 ms) 875 ms: Jakt::Detail::index_of<$> (284 times, avg 3 ms) 619 ms: JaktInternal::ExplicitValueOrControlFlow<$>::ExplicitValueOrControlF... (315 times, avg 1 ms) 386 ms: Jakt::Detail::VariantConstructors<$>::VariantConstructors (450 times, avg 0 ms) 366 ms: Jakt::Variant<$> (327 times, avg 1 ms) 311 ms: JaktInternal::Array<$>::create_with (53 times, avg 5 ms) 250 ms: Jakt::ErrorOr<$> (222 times, avg 1 ms) 223 ms: JaktInternal::Array<$>::create_empty (56 times, avg 3 ms) 148 ms: JaktInternal::ExplicitValueOrControlFlow<$> (105 times, avg 1 ms) 135 ms: Jakt::StringBuilder::appendff<$> (85 times, avg 1 ms) 133 ms: Jakt::adopt_nonnull_ref_or_enomem<$> (78 times, avg 1 ms) 128 ms: Jakt::Variant<$>::Variant (44 times, avg 2 ms) 123 ms: Jakt::VariadicFormatParams<$>::VariadicFormatParams (88 times, avg 1 ms) 100 ms: Jakt::Formatter<$>::format (67 times, avg 1 ms) 100 ms: Jakt::__format_value<$> (70 times, avg 1 ms) 96 ms: Jakt::Variant<$>::set<$> (29 times, avg 3 ms) 88 ms: JaktInternal::Dictionary<$>::create_with_entries (12 times, avg 7 ms) 84 ms: Jakt::Detail::VariantConstructors<$> (48 times, avg 1 ms) 69 ms: JaktInternal::Dictionary<$>::set (12 times, avg 5 ms) 68 ms: Jakt::HashMap<$>::set (12 times, avg 5 ms) 67 ms: Jakt::HashTable<$>::try_set<$> (13 times, avg 5 ms) 61 ms: Jakt::Detail::Variant<$>::move_ (18 times, avg 3 ms) 52 ms: Jakt::HashTable<$>::try_rehash (13 times, avg 4 ms) 52 ms: JaktInternal::ExplicitValueOrControlFlow<$>::release_return (40 times, avg 1 ms) 50 ms: Jakt::Detail::Variant<$>::copy_ (19 times, avg 2 ms) 49 ms: Jakt::HashTable<$>::try_lookup_for_writing (13 times, avg 3 ms) 48 ms: JaktInternal::Dictionary<$>::create_empty (12 times, avg 4 ms) 47 ms: Jakt::Variant<$>::~Variant (16 times, avg 2 ms) 45 ms: Jakt::Detail::Variant<$>::delete_ (15 times, avg 3 ms) **** Functions that took longest to compile: 100 ms: Jakt::typechecker::CheckedExpression::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 75 ms: Jakt::parser::ParsedExpression::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 74 ms: Jakt::typechecker::Typechecker::typecheck_binary_operation(Jakt::Non... (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 69 ms: Jakt::lexer::Token::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 47 ms: Jakt::typechecker::Typechecker::typecheck_expression(Jakt::NonnullRe... (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 43 ms: Jakt::parser::ParsedStatement::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 40 ms: Jakt::codegen::CodeGenerator::codegen_expression(Jakt::NonnullRefPtr... (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 36 ms: Jakt::main(JaktInternal::Array) (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 33 ms: Jakt::typechecker::CheckedStatement::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 31 ms: Jakt::parser::ParsedType::debug_description() const (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 28 ms: Jakt::typechecker::Typechecker::typecheck_enum(Jakt::parser::ParsedR... (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 28 ms: Jakt::typechecker::Typechecker::typecheck_typename(Jakt::NonnullRefP... (/home/ilya/devel/external/jakt-pr1038/build/jakt_stage1_main.cpp) 27 ms: Jakt::ide::get_type_signature(Jakt::NonnullRefPtr::format(Jakt::FormatBuilder&, JaktInternal::Array... (50 times, avg 2 ms) 74 ms: Jakt::typechecker::Typechecker::typecheck_binary_operation(Jakt::Non... (1 times, avg 74 ms) 52 ms: JaktInternal::Array<$>::create_with(std::initializer_list<$>) (56 times, avg 0 ms) 47 ms: Jakt::typechecker::Typechecker::typecheck_expression(Jakt::NonnullRe... (1 times, avg 47 ms) 40 ms: Jakt::codegen::CodeGenerator::codegen_expression(Jakt::NonnullRefPtr... (1 times, avg 40 ms) 39 ms: JaktInternal::Array<$>::create_empty() (56 times, avg 0 ms) 36 ms: Jakt::main(JaktInternal::Array<$>) (1 times, avg 36 ms) 28 ms: Jakt::typechecker::Typechecker::typecheck_typename(Jakt::NonnullRefP... (1 times, avg 28 ms) 27 ms: Jakt::ide::get_type_signature(Jakt::NonnullRefPtr::format(Jakt::FormatBuilder&, JaktInternal::Dicti... (8 times, avg 2 ms) 21 ms: Jakt::codegen::CodeGenerator::codegen_statement(Jakt::NonnullRefPtr<... (1 times, avg 21 ms) 17 ms: Jakt::typechecker::Typechecker::typecheck_var_decl(Jakt::parser::Par... (1 times, avg 17 ms) 15 ms: Jakt::typechecker::Typechecker::check_types_for_compat(Jakt::typeche... (1 times, avg 15 ms) 14 ms: Jakt::typechecker::Typechecker::typecheck_match(Jakt::NonnullRefPtr<... (1 times, avg 14 ms) 14 ms: Jakt::codegen::CodeGenerator::codegen_namespace(Jakt::NonnullRefPtr<... (1 times, avg 14 ms) 13 ms: Jakt::ide::find_span_in_statement(Jakt::NonnullRefPtr::create_with_entries(std::initializer_li... (12 times, avg 0 ms) 11 ms: Jakt::typechecker::Typechecker::typecheck_for(Jakt::String, Jakt::ut... (1 times, avg 11 ms) 10 ms: Jakt::typechecker::Typechecker::typecheck_match(Jakt::NonnullRefPtr<... (1 times, avg 10 ms) 10 ms: Jakt::typechecker::Typechecker::typecheck_match(Jakt::NonnullRefPtr<... (1 times, avg 10 ms) 10 ms: Jakt::typechecker::Typechecker::substitute_typevars_in_type_helper(J... (1 times, avg 10 ms) 10 ms: Jakt::typechecker::Typechecker::typecheck_function_predecl(Jakt::par... (1 times, avg 10 ms) *** Expensive headers: 171 ms: /home/ilya/devel/external/jakt-pr1038/runtime/lib.h (included 1 times, avg 171 ms), included via: jakt_stage1_main.cpp.o (171 ms) done in 0.0s. ```
maddanio commented 2 years ago

We don't do c++ exceptions here

We have our own Error for that, which is supposed to be cheaper

I understand that and am completely on your side. I am just saying disabling exceptions also means you cannot call throwing functions in extern c++ code, even if you directly surround them with a catch-all clause. It severely limits interoperability

maddanio commented 2 years ago

Inside the serenity eco system that won't be an issue, but since a) serenity does have ports and you may wanna interact with non serenity libraries from jakt and b) this repo is separate and thus possibly also intended for non serenity use i thought this maybe something to consider

drunderscore commented 2 years ago

Serenity ports do not build with exceptions last I checked -- the Diablo port had to have them removed and replaced with assertions IIRC.

ADKaster commented 2 years ago

Serenity ports can choose to use whatever exception settings they like -- just if they end up interacting with Serenity native libraries (LibGUI for example), and they throw an exception through our libraries, we make no promises about what exception safety guarantees happens there. The C library has none, like basically every platform, and the STL is always either libc++ or libstdc++. Normally GUI stuff is funneled through SDL2, which again, with a C API I would hope no one is passing callbacks that can throw an exception through SDL2 layers.

If someone really wants to call throwing C++ code from jakt... that seems out of scope for this performance issue anyway, and should be a different issue :^)