diff options
author | Abseil Team <absl-team@google.com> | 2024-03-05 10:36:18 (GMT) |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-03-05 10:37:09 (GMT) |
commit | b9059aaa4cb93b3fb177670cbfd9c6d0690af37e (patch) | |
tree | 4e7561407a284cb66b326fb0a40b5ac0ec13c65d /googletest/src | |
parent | 926f681a902cbfb84a863dafb77175f4a25be6ea (diff) | |
download | googletest-b9059aaa4cb93b3fb177670cbfd9c6d0690af37e.zip googletest-b9059aaa4cb93b3fb177670cbfd9c6d0690af37e.tar.gz googletest-b9059aaa4cb93b3fb177670cbfd9c6d0690af37e.tar.bz2 |
Optimize Google Test process startup
Google Test performs hidden test registration during process
startup. For test binaries that contain a large number of tests, this
registration can be costly. In this CL, we reduce the overhead of
registration via several tactics:
- Treat CodeLocation and FilePath as value types, using std::move to
pass them around.
- Reduce string copies in various places by either passing std::string
values via std::move, or passing const-refs to std::string instances.
- Use std::to_string to stringify an int in DefaultParamName rather than
a std::stringstream.
- Pull some std::string instances out of nested loops in
ParameterizedTestSuiteInfo::RegisterTests so as to reuse some
allocations, and replace stringstream with ordinary string appends.
- Use std::unordered_map in UnitTestImpl::GetTestSuite and
ParameterizedTestSuiteRegistry::GetTestSuitePatternHolder to spend a
little memory to turn O(N) lookups into constant time lookpus.
- Use range-based for loops in various places.
- Use emplace-ish methods to add to containers where appropriate.
All together, these changes reduce the overall runtime of a series of 50
death tests in a single Chromium test executable by ~38% due to the
fact that the registration costs are paid in every death test's child
process.
PiperOrigin-RevId: 612763676
Change-Id: I1f46e012ccb9004c009e1027e4f7c38780ffb9e2
Diffstat (limited to 'googletest/src')
-rw-r--r-- | googletest/src/gtest-internal-inl.h | 20 | ||||
-rw-r--r-- | googletest/src/gtest.cc | 117 |
2 files changed, 66 insertions, 71 deletions
diff --git a/googletest/src/gtest-internal-inl.h b/googletest/src/gtest-internal-inl.h index 6dea34f..6a7f4dd 100644 --- a/googletest/src/gtest-internal-inl.h +++ b/googletest/src/gtest-internal-inl.h @@ -46,6 +46,7 @@ #include <memory> #include <set> #include <string> +#include <unordered_map> #include <vector> #include "gtest/internal/gtest-port.h" @@ -649,13 +650,15 @@ class GTEST_API_ UnitTestImpl { // this is not a typed or a type-parameterized test. // set_up_tc: pointer to the function that sets up the test suite // tear_down_tc: pointer to the function that tears down the test suite - TestSuite* GetTestSuite(const char* test_suite_name, const char* type_param, + TestSuite* GetTestSuite(const std::string& test_suite_name, + const char* type_param, internal::SetUpTestSuiteFunc set_up_tc, internal::TearDownTestSuiteFunc tear_down_tc); // Legacy API is deprecated but still available #ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - TestCase* GetTestCase(const char* test_case_name, const char* type_param, + TestCase* GetTestCase(const std::string& test_case_name, + const char* type_param, internal::SetUpTestSuiteFunc set_up_tc, internal::TearDownTestSuiteFunc tear_down_tc) { return GetTestSuite(test_case_name, type_param, set_up_tc, tear_down_tc); @@ -681,13 +684,13 @@ class GTEST_API_ UnitTestImpl { // AddTestInfo(), which is called to register a TEST or TEST_F // before main() is reached. if (original_working_dir_.IsEmpty()) { - original_working_dir_.Set(FilePath::GetCurrentDir()); + original_working_dir_ = FilePath::GetCurrentDir(); GTEST_CHECK_(!original_working_dir_.IsEmpty()) << "Failed to get the current working directory."; } #endif // GTEST_HAS_FILE_SYSTEM - GetTestSuite(test_info->test_suite_name(), test_info->type_param(), + GetTestSuite(test_info->test_suite_name_, test_info->type_param(), set_up_tc, tear_down_tc) ->AddTestInfo(test_info); } @@ -823,6 +826,12 @@ class GTEST_API_ UnitTestImpl { bool catch_exceptions() const { return catch_exceptions_; } private: + struct CompareTestSuitesByPointer { + bool operator()(const TestSuite* lhs, const TestSuite* rhs) const { + return lhs->name_ < rhs->name_; + }; + }; + friend class ::testing::UnitTest; // Used by UnitTest::Run() to capture the state of @@ -873,6 +882,9 @@ class GTEST_API_ UnitTestImpl { // elements in the vector. std::vector<TestSuite*> test_suites_; + // The set of TestSuites by name. + std::unordered_map<std::string, TestSuite*> test_suites_by_name_; + // Provides a level of indirection for the test suite list to allow // easy shuffling and restoring the test suite order. The i-th // element of this vector is the index of the i-th test suite in the diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index c5f22bb..ca9f524 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -578,7 +578,7 @@ void InsertSyntheticTestCase(const std::string& name, CodeLocation location, void RegisterTypeParameterizedTestSuite(const char* test_suite_name, CodeLocation code_location) { GetUnitTestImpl()->type_parameterized_test_registry().RegisterTestSuite( - test_suite_name, code_location); + test_suite_name, std::move(code_location)); } void RegisterTypeParameterizedTestSuiteInstantiation(const char* case_name) { @@ -589,7 +589,7 @@ void RegisterTypeParameterizedTestSuiteInstantiation(const char* case_name) { void TypeParameterizedTestSuiteRegistry::RegisterTestSuite( const char* test_suite_name, CodeLocation code_location) { suites_.emplace(std::string(test_suite_name), - TypeParameterizedTestSuiteInfo(code_location)); + TypeParameterizedTestSuiteInfo(std::move(code_location))); } void TypeParameterizedTestSuiteRegistry::RegisterInstantiation( @@ -801,7 +801,7 @@ class UnitTestFilter { // Returns true if and only if name matches at least one of the patterns in // the filter. bool MatchesName(const std::string& name) const { - return exact_match_patterns_.count(name) > 0 || + return exact_match_patterns_.find(name) != exact_match_patterns_.end() || std::any_of(glob_patterns_.begin(), glob_patterns_.end(), [&name](const std::string& pattern) { return PatternMatchesString( @@ -2740,18 +2740,16 @@ bool Test::IsSkipped() { // Constructs a TestInfo object. It assumes ownership of the test factory // object. -TestInfo::TestInfo(const std::string& a_test_suite_name, - const std::string& a_name, const char* a_type_param, - const char* a_value_param, +TestInfo::TestInfo(std::string a_test_suite_name, std::string a_name, + const char* a_type_param, const char* a_value_param, internal::CodeLocation a_code_location, internal::TypeId fixture_class_id, internal::TestFactoryBase* factory) - : test_suite_name_(a_test_suite_name), - // begin()/end() is MSVC 17.3.3 ASAN crash workaround (GitHub issue #3997) - name_(a_name.begin(), a_name.end()), + : test_suite_name_(std::move(a_test_suite_name)), + name_(std::move(a_name)), type_param_(a_type_param ? new std::string(a_type_param) : nullptr), value_param_(a_value_param ? new std::string(a_value_param) : nullptr), - location_(a_code_location), + location_(std::move(a_code_location)), fixture_class_id_(fixture_class_id), should_run_(false), is_disabled_(false), @@ -2784,19 +2782,19 @@ namespace internal { // The newly created TestInfo instance will assume // ownership of the factory object. TestInfo* MakeAndRegisterTestInfo( - const char* test_suite_name, const char* name, const char* type_param, + std::string test_suite_name, const char* name, const char* type_param, const char* value_param, CodeLocation code_location, TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) { TestInfo* const test_info = - new TestInfo(test_suite_name, name, type_param, value_param, - code_location, fixture_class_id, factory); + new TestInfo(std::move(test_suite_name), name, type_param, value_param, + std::move(code_location), fixture_class_id, factory); GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); return test_info; } void ReportInvalidTestSuiteType(const char* test_suite_name, - CodeLocation code_location) { + const CodeLocation& code_location) { Message errors; errors << "Attempted redefinition of test suite " << test_suite_name << ".\n" @@ -2948,7 +2946,7 @@ int TestSuite::total_test_count() const { // this is not a typed or a type-parameterized test suite. // set_up_tc: pointer to the function that sets up the test suite // tear_down_tc: pointer to the function that tears down the test suite -TestSuite::TestSuite(const char* a_name, const char* a_type_param, +TestSuite::TestSuite(const std::string& a_name, const char* a_type_param, internal::SetUpTestSuiteFunc set_up_tc, internal::TearDownTestSuiteFunc tear_down_tc) : name_(a_name), @@ -3836,11 +3834,10 @@ void TestEventRepeater::Append(TestEventListener* listener) { } TestEventListener* TestEventRepeater::Release(TestEventListener* listener) { - for (size_t i = 0; i < listeners_.size(); ++i) { - if (listeners_[i] == listener) { - listeners_.erase(listeners_.begin() + static_cast<int>(i)); - return listener; - } + auto iter = std::find(listeners_.begin(), listeners_.end(), listener); + if (iter != listeners_.end()) { + listeners_.erase(iter); + return listener; } return nullptr; @@ -3851,20 +3848,21 @@ TestEventListener* TestEventRepeater::Release(TestEventListener* listener) { #define GTEST_REPEATER_METHOD_(Name, Type) \ void TestEventRepeater::Name(const Type& parameter) { \ if (forwarding_enabled_) { \ - for (size_t i = 0; i < listeners_.size(); i++) { \ - listeners_[i]->Name(parameter); \ + for (auto* listener : listeners_) { \ + listener->Name(parameter); \ } \ } \ } // This defines a member that forwards the call to all listeners in reverse // order. -#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ - void TestEventRepeater::Name(const Type& parameter) { \ - if (forwarding_enabled_) { \ - for (size_t i = listeners_.size(); i != 0; i--) { \ - listeners_[i - 1]->Name(parameter); \ - } \ - } \ +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ + void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + const auto end = listeners_.rend(); \ + for (auto scan = listeners_.rbegin(); scan != end; ++scan) { \ + (*scan)->Name(parameter); \ + } \ + } \ } GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) @@ -3894,8 +3892,8 @@ GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, int iteration) { if (forwarding_enabled_) { - for (size_t i = 0; i < listeners_.size(); i++) { - listeners_[i]->OnTestIterationStart(unit_test, iteration); + for (auto* listener : listeners_) { + listener->OnTestIterationStart(unit_test, iteration); } } } @@ -3903,8 +3901,9 @@ void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, int iteration) { if (forwarding_enabled_) { - for (size_t i = listeners_.size(); i > 0; i--) { - listeners_[i - 1]->OnTestIterationEnd(unit_test, iteration); + const auto end = listeners_.rend(); + for (auto scan = listeners_.rbegin(); scan != end; ++scan) { + (*scan)->OnTestIterationEnd(unit_test, iteration); } } } @@ -5746,29 +5745,6 @@ void UnitTestImpl::PostFlagParsingInit() { } } -// A predicate that checks the name of a TestSuite against a known -// value. -// -// This is used for implementation of the UnitTest class only. We put -// it in the anonymous namespace to prevent polluting the outer -// namespace. -// -// TestSuiteNameIs is copyable. -class TestSuiteNameIs { - public: - // Constructor. - explicit TestSuiteNameIs(const std::string& name) : name_(name) {} - - // Returns true if and only if the name of test_suite matches name_. - bool operator()(const TestSuite* test_suite) const { - return test_suite != nullptr && - strcmp(test_suite->name(), name_.c_str()) == 0; - } - - private: - std::string name_; -}; - // Finds and returns a TestSuite with the given name. If one doesn't // exist, creates one and returns it. It's the CALLER'S // RESPONSIBILITY to ensure that this function is only called WHEN THE @@ -5782,19 +5758,27 @@ class TestSuiteNameIs { // set_up_tc: pointer to the function that sets up the test suite // tear_down_tc: pointer to the function that tears down the test suite TestSuite* UnitTestImpl::GetTestSuite( - const char* test_suite_name, const char* type_param, + const std::string& test_suite_name, const char* type_param, internal::SetUpTestSuiteFunc set_up_tc, internal::TearDownTestSuiteFunc tear_down_tc) { - // Can we find a TestSuite with the given name? - const auto test_suite = - std::find_if(test_suites_.rbegin(), test_suites_.rend(), - TestSuiteNameIs(test_suite_name)); + // During initialization, all TestInfos for a given suite are added in + // sequence. To optimize this case, see if the most recently added suite is + // the one being requested now. + if (!test_suites_.empty() && + (*test_suites_.rbegin())->name_ == test_suite_name) { + return *test_suites_.rbegin(); + } - if (test_suite != test_suites_.rend()) return *test_suite; + // Fall back to searching the collection. + auto item_it = test_suites_by_name_.find(test_suite_name); + if (item_it != test_suites_by_name_.end()) { + return item_it->second; + } - // No. Let's create one. + // Not found. Create a new instance. auto* const new_test_suite = new TestSuite(test_suite_name, type_param, set_up_tc, tear_down_tc); + test_suites_by_name_.emplace(test_suite_name, new_test_suite); const UnitTestFilter death_test_suite_filter(kDeathTestSuiteFilter); // Is this a death test suite? @@ -6146,12 +6130,11 @@ int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { int num_runnable_tests = 0; int num_selected_tests = 0; for (auto* test_suite : test_suites_) { - const std::string& test_suite_name = test_suite->name(); + const std::string& test_suite_name = test_suite->name_; test_suite->set_should_run(false); - for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { - TestInfo* const test_info = test_suite->test_info_list()[j]; - const std::string test_name(test_info->name()); + for (TestInfo* test_info : test_suite->test_info_list()) { + const std::string& test_name = test_info->name_; // A test is disabled if test suite name or test name matches // kDisableTestFilter. const bool is_disabled = |