Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorMartin Hořeňovský2026-09-03 20:16:55 +0200
committerMartin Hořeňovský2026-09-03 20:22:37 +0200
commitde1330d036168492a04b649a1fa0593faece6f74 (patch)
tree09fae90a513ef3ae05c2027279715276c8b3f910 /src
parent317ac1ed4c0bb6e6b91eafc817e05c488feffcb3 (diff)
downloadcatch3-de1330d036168492a04b649a1fa0593faece6f74.tar.gz
Avoid bad compile time scaling of test registration with optimizations
GCC is especially bad, as it had quadratic scaling in the number of registered test cases in a TU, but Clang also scaled superlinearly, and this commit fixes both. AIUI: 1) Each `TEST_CASE` creates a separate global fragment to handle creating the `unique_ptr<ITestInvoker>`, moving the instance into AutoReg, destroying the old instance, etc, etc. 2) With optimizations enabled, the compiler merges all these global fragments into one big global fragment. 3) Then the compiler runs various optimizations passes on this new global fragment. 4) Because some of the passes scale superlinearly, the compilation times scale superlinearly in the number of `TEST_CASE` in a translation unit. For bonus point, no optimization actually happens. This commit fixes the problem (for non-LTO builds) by adding new `AutoReg` constructor, one that takes the function pointer raw, instead of wrapped in `ITestInvoker`. By delaying the construction of the `ITestInvoker` into the outlined constructor, we no longer create big global fragments for the compiler to optimize.
Diffstat (limited to 'src')
-rw-r--r--src/catch2/internal/catch_template_test_registry.hpp4
-rw-r--r--src/catch2/internal/catch_test_registry.cpp9
-rw-r--r--src/catch2/internal/catch_test_registry.hpp21
3 files changed, 29 insertions, 5 deletions
diff --git a/src/catch2/internal/catch_template_test_registry.hpp b/src/catch2/internal/catch_template_test_registry.hpp
index ed25251a..47da95bb 100644
--- a/src/catch2/internal/catch_template_test_registry.hpp
+++ b/src/catch2/internal/catch_template_test_registry.hpp
@@ -136,7 +136,7 @@
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
- (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\
+ (void)expander{(Catch::AutoReg( &TestFuncName<Types>, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\
} \
}; \
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
@@ -182,7 +182,7 @@
void reg_tests() { \
size_t index = 0; \
using expander = size_t[]; \
- (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\
+ (void)expander{(Catch::AutoReg( &TestFunc<Types>, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\
} \
};\
static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
diff --git a/src/catch2/internal/catch_test_registry.cpp b/src/catch2/internal/catch_test_registry.cpp
index d017c50e..3a2f006a 100644
--- a/src/catch2/internal/catch_test_registry.cpp
+++ b/src/catch2/internal/catch_test_registry.cpp
@@ -66,6 +66,15 @@ namespace Catch {
return Detail::make_unique<TestInvokerAsFunction>( testAsFunction );
}
+ AutoReg::AutoReg( void ( *testAsFunction )(),
+ SourceLineInfo const& lineInfo,
+ StringRef classOrMethod,
+ NameAndTags const& nameAndTags ) noexcept:
+ AutoReg( makeTestInvoker( testAsFunction ),
+ lineInfo,
+ classOrMethod,
+ nameAndTags ) {}
+
AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept {
CATCH_TRY {
getMutableRegistryHub()
diff --git a/src/catch2/internal/catch_test_registry.hpp b/src/catch2/internal/catch_test_registry.hpp
index 213b2bdc..e81c728d 100644
--- a/src/catch2/internal/catch_test_registry.hpp
+++ b/src/catch2/internal/catch_test_registry.hpp
@@ -85,7 +85,22 @@ struct NameAndTags {
};
struct AutoReg : Detail::NonCopyable {
- AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept;
+ AutoReg( Detail::unique_ptr<ITestInvoker> invoker,
+ SourceLineInfo const& lineInfo,
+ StringRef classOrMethod,
+ NameAndTags const& nameAndTags ) noexcept;
+
+ /**
+ * Overload that takes the simple function pointer directly.
+ *
+ * Useful to keep compilation costs low; the unique_ptr overload
+ * combined with `makeTestInvoker` causes quadratic compilation times
+ * with GCC + optimizations (and super-linear with Clang + optims).
+ */
+ AutoReg( void ( *testAsFunction )(),
+ SourceLineInfo const& lineInfo,
+ StringRef classOrMethod,
+ NameAndTags const& nameAndTags ) noexcept;
};
} // end namespace Catch
@@ -111,7 +126,7 @@ struct AutoReg : Detail::NonCopyable {
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
- namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+ namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
static void TestName()
#define INTERNAL_CATCH_TESTCASE( ... ) \
@@ -214,7 +229,7 @@ static int catchInternalSectionHint = 0;
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \
- Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Function, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
} while(false)