[Lldb-commits] [lldb] 29faf0a - [lldb] Speed up thread plan pruning for large thread counts (#236971)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Oct 30 08:56:41 PDT 2026
Author: Bar Soloveychik
Date: 2026-10-30T08:56:36-07:00
New Revision: 29faf0ab5fa567e7ca1fec8f2362833a24bc4abd
URL: https://github.com/llvm/llvm-project/commit/29faf0ab5fa567e7ca1fec8f2362833a24bc4abd
DIFF: https://github.com/llvm/llvm-project/commit/29faf0ab5fa567e7ca1fec8f2362833a24bc4abd.diff
LOG: [lldb] Speed up thread plan pruning for large thread counts (#236971)
Reland #236269 after its revert in #236903.
Index the current thread IDs once when pruning thread plans, replacing
per plan linear searches with hash set membership checks. Preserve the
no-refresh contract and missing-plan controls.
Changes since the original commit:
- Fixed the test setup by setting the host platform before creating the
debugger, since I broke the Windows builder.
- Updated the fixture to use `SubsystemRAII`
- Fixed the LLVM header
The implementation was unchanged.
Co-authored-by: Bar Soloveychik <barsolo at fb.com>
Added:
lldb/unittests/Target/ThreadPlanStackTest.cpp
Modified:
lldb/source/Target/ThreadPlanStack.cpp
lldb/unittests/Target/CMakeLists.txt
Removed:
################################################################################
diff --git a/lldb/source/Target/ThreadPlanStack.cpp b/lldb/source/Target/ThreadPlanStack.cpp
index 4027209d257e15..0d76774c1f0086 100644
--- a/lldb/source/Target/ThreadPlanStack.cpp
+++ b/lldb/source/Target/ThreadPlanStack.cpp
@@ -13,6 +13,8 @@
#include "lldb/Target/ThreadPlan.h"
#include "lldb/Utility/Log.h"
+#include <unordered_set>
+
using namespace lldb;
using namespace lldb_private;
@@ -424,19 +426,24 @@ void ThreadPlanStackMap::Update(ThreadList ¤t_threads,
}
}
- // If we aren't reaping missing threads at this point,
- // we are done.
- if (!delete_missing)
+ // If we aren't reaping missing threads or have no plans, we are done.
+ if (!delete_missing || m_plans_list.empty())
return;
- // Otherwise scan for absent TID's.
+
+ // Index the already-current thread list rather than searching it separately
+ // for every plan. Repeated linear searches become prohibitively expensive
+ // for targets with large thread counts.
+ std::unordered_set<lldb::tid_t> live_tids;
+ live_tids.reserve(current_threads.GetSize(/*can_update=*/false));
+ for (const auto &thread : current_threads.Threads())
+ live_tids.insert(thread->GetID());
+
std::vector<lldb::tid_t> missing_threads;
// If we are going to delete plans from the plan stack,
// then scan for absent TID's:
for (auto &thread_plans : m_plans_list) {
lldb::tid_t cur_tid = thread_plans.first;
- ThreadSP thread_sp =
- current_threads.FindThreadByID(cur_tid, /*can_update=*/false);
- if (!thread_sp)
+ if (live_tids.find(cur_tid) == live_tids.end())
missing_threads.push_back(cur_tid);
}
for (lldb::tid_t tid : missing_threads) {
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index 36fc582e41e1a2..d7b2bfe9298593 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -20,6 +20,7 @@ add_lldb_unittest(TargetTests
SummaryStatisticsTest.cpp
TargetAPIMutexTest.cpp
TargetGroupTest.cpp
+ ThreadPlanStackTest.cpp
FindFileTest.cpp
LINK_COMPONENTS
diff --git a/lldb/unittests/Target/ThreadPlanStackTest.cpp b/lldb/unittests/Target/ThreadPlanStackTest.cpp
new file mode 100644
index 00000000000000..3afb9832baa74b
--- /dev/null
+++ b/lldb/unittests/Target/ThreadPlanStackTest.cpp
@@ -0,0 +1,186 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/ThreadPlanStack.h"
+#include "Plugins/Platform/Linux/PlatformLinux.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "TestingSupport/TestUtilities.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Target/Platform.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadList.h"
+#include "lldb/Utility/Listener.h"
+#include "gtest/gtest.h"
+
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+using namespace lldb;
+using namespace lldb_private;
+
+namespace {
+class TestProcess : public Process {
+public:
+ TestProcess(TargetSP target, ListenerSP listener)
+ : Process(target, listener) {}
+
+ bool CanDebug(TargetSP, bool) override { return true; }
+ Status DoDestroy() override { return {}; }
+ void RefreshStateAfterStop() override {}
+ size_t DoReadMemory(const ProcessAddress &, void *, size_t,
+ Status &) override {
+ return 0;
+ }
+ bool DoUpdateThreadList(ThreadList &, ThreadList &new_threads) override {
+ if (!refresh_threads)
+ return false;
+ ++refresh_count;
+ for (const auto &thread : next_threads)
+ new_threads.AddThread(thread);
+ return true;
+ }
+ llvm::StringRef GetPluginName() override { return "thread-plan-test"; }
+
+ ThreadPlanStackMap &Plans() { return m_thread_plans; }
+ void PrepareThreadRefresh(std::vector<ThreadSP> threads) {
+ next_threads = std::move(threads);
+ refresh_threads = true;
+ m_current_private_state_thread_sp->SetPrivateState(eStateStopped);
+ m_mod_id.BumpStopID();
+ }
+
+ unsigned refresh_count = 0;
+
+private:
+ bool refresh_threads = false;
+ std::vector<ThreadSP> next_threads;
+};
+
+class TestThread : public Thread {
+public:
+ using Thread::Thread;
+ ~TestThread() override { DestroyThread(); }
+ void RefreshStateAfterStop() override {}
+ RegisterContextSP GetRegisterContext() override { return nullptr; }
+ RegisterContextSP CreateRegisterContextForFrame(StackFrame *) override {
+ return nullptr;
+ }
+ bool CalculateStopInfo() override { return false; }
+};
+
+class ThreadPlanStackMapTest : public testing::Test {
+ SubsystemRAII<FileSystem, HostInfo> subsystems;
+
+protected:
+ void SetUp() override {
+ std::call_once(TestUtilities::g_debugger_initialize_flag,
+ [] { Debugger::Initialize(nullptr); });
+ // A standalone test shard may not have a native host platform installed.
+ Platform::SetHostPlatform(nullptr);
+ ArchSpec arch("x86_64-pc-linux");
+ PlatformSP platform =
+ platform_linux::PlatformLinux::CreateInstance(true, &arch);
+ ASSERT_TRUE(platform);
+ Platform::SetHostPlatform(platform);
+ debugger = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger);
+ TargetSP target;
+ ASSERT_TRUE(debugger->GetTargetList()
+ .CreateTarget(*debugger, "", arch, eLoadDependentsNo,
+ platform, target)
+ .Success());
+ process = std::make_shared<TestProcess>(
+ target, Listener::MakeListener("thread-plan-test"));
+ }
+
+ void TearDown() override {
+ process.reset();
+ if (debugger)
+ Debugger::Destroy(debugger);
+ }
+
+ ThreadSP AddThread(tid_t tid) {
+ auto thread = std::make_shared<TestThread>(*process, tid);
+ process->GetThreadList().AddThread(thread);
+ return thread;
+ }
+ void Update(bool delete_missing = true, bool check_for_new = true) {
+ process->Plans().Update(process->GetThreadList(), delete_missing,
+ check_for_new);
+ }
+
+ DebuggerSP debugger;
+ std::shared_ptr<TestProcess> process;
+};
+} // namespace
+
+TEST_F(ThreadPlanStackMapTest, RemovesOnlyMissingPlans) {
+ const tid_t live_tid = std::numeric_limits<tid_t>::max() - 1;
+ AddThread(live_tid);
+ auto removed = AddThread(42);
+ Update();
+ auto *survivor = process->Plans().Find(live_tid);
+ ASSERT_NE(nullptr, survivor);
+ ASSERT_NE(nullptr, process->Plans().Find(42));
+ process->GetThreadList().RemoveThreadByID(42, false);
+ Update();
+ EXPECT_EQ(survivor, process->Plans().Find(live_tid));
+ EXPECT_EQ(nullptr, process->Plans().Find(42));
+}
+
+TEST_F(ThreadPlanStackMapTest, PreservesMissingPlansWhenRequested) {
+ auto removed = AddThread(42);
+ Update();
+ auto *plans = process->Plans().Find(42);
+ ASSERT_NE(nullptr, plans);
+ process->GetThreadList().Clear();
+ Update(false, false);
+ EXPECT_EQ(plans, process->Plans().Find(42));
+}
+
+TEST_F(ThreadPlanStackMapTest, RemovesAllPlansForAnEmptyList) {
+ auto first = AddThread(7);
+ auto second = AddThread(42);
+ Update();
+ ASSERT_NE(nullptr, process->Plans().Find(7));
+ ASSERT_NE(nullptr, process->Plans().Find(42));
+ process->GetThreadList().Clear();
+ Update(true, false);
+ EXPECT_EQ(nullptr, process->Plans().Find(7));
+ EXPECT_EQ(nullptr, process->Plans().Find(42));
+}
+
+TEST_F(ThreadPlanStackMapTest, DoesNotRefreshProcessWhilePruningSnapshot) {
+ auto survivor = AddThread(7);
+ auto removed = AddThread(42);
+ Update();
+ ASSERT_NE(nullptr, process->Plans().Find(7));
+ ASSERT_NE(nullptr, process->Plans().Find(42));
+ process->PrepareThreadRefresh({survivor});
+
+ // The caller supplies a current snapshot. Pruning must not refresh the
+ // process's separate, stale list as a side effect of inspecting this one.
+ ThreadList snapshot(*process);
+ snapshot.AddThread(survivor);
+ snapshot.SetStopID(process->GetStopID());
+ process->Plans().Update(snapshot, true, false);
+
+ EXPECT_EQ(0u, process->refresh_count);
+ EXPECT_NE(nullptr, process->Plans().Find(7));
+ EXPECT_EQ(nullptr, process->Plans().Find(42));
+
+ process->UpdateThreadListIfNeeded();
+ EXPECT_EQ(1u, process->refresh_count);
+}
More information about the lldb-commits
mailing list