[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 &current_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