[PATCH v2 1/1] gdbserver: add process specific thread list and map.
Simon Marchi
simark@simark.ca
Tue Oct 8 20:12:30 GMT 2024
Hi Stephan,
Here's a first pass, non exhaustive, but I feel like there are enough
comments for now.
On 2024-09-03 07:34, Stephan Rohr wrote:
> Replace the servers global thread list with a process specific thread
> list and a ptid -> thread map similar to 'inferior::ptid_thread_map' on
> GDB side. Optimize the 'find_thread' and 'find_thread_ptid' functions
> to use std::unordered_map::find for faster lookup of threads without
> iterating over all processes and threads, if applicable. This becomes
> important when debugging applications with a large thread count, e.g.,
> in the context of GPU debugging.
>
> Add the following functions for performance improvements by
> limiting the iterator range, if applicable:
>
> - for_each_thread (ptid_t ptid, Func func)
> - for_each_thread (process_info *process, Func func)
> - find_thread_in_random (ptid_t ptid, Func func)
> ---
> gdbserver/gdbthread.h | 146 +++++++++++++++++++++++++++++++----------
> gdbserver/inferiors.cc | 49 ++++++++++----
> gdbserver/inferiors.h | 29 +++++++-
> gdbserver/server.cc | 59 ++++++++++++++---
> 4 files changed, 226 insertions(+), 57 deletions(-)
>
> diff --git a/gdbserver/gdbthread.h b/gdbserver/gdbthread.h
> index c5a5498088f..f7562ac54a1 100644
> --- a/gdbserver/gdbthread.h
> +++ b/gdbserver/gdbthread.h
> @@ -85,7 +85,7 @@ struct thread_info
> gdb_thread_options thread_options = 0;
> };
>
> -extern std::list<thread_info *> all_threads;
> +extern std::list<process_info *> all_processes;
Since all_processes is defined in inferiors.cc, can you puts its
declaration in inferiors.h?
>
> void remove_thread (struct thread_info *thread);
> struct thread_info *add_thread (ptid_t ptid, void *target_data);
> @@ -100,17 +100,20 @@ struct thread_info *find_thread_ptid (ptid_t ptid);
> found. */
> struct thread_info *find_any_thread_of_pid (int pid);
>
> -/* Find the first thread for which FUNC returns true. Return NULL if no thread
> - satisfying FUNC is found. */
> +/* Find the first thread for which FUNC returns true, only consider
> + threads in the thread list of PROCESS. Return NULL if no thread
> + that satisfies FUNC is found. */
I suggest this more concise formulation:
/* Find the first thread of PROCESS for which FUNC returns true.
Return NULL if no thread that satisfies FUNC is found. */
>
> template <typename Func>
> static thread_info *
> -find_thread (Func func)
> +find_thread (process_info *process, Func func)
> {
> - std::list<thread_info *>::iterator next, cur = all_threads.begin ();
> + std::list<thread_info *> *thread_list = get_thread_list (process);
> + std::list<thread_info *>::iterator next, cur = thread_list->begin ();
>
> - while (cur != all_threads.end ())
> + while (cur != thread_list->end ())
> {
> + /* FUNC may alter the current iterator. */
> next = cur;
> next++;
It seems weird for a find_thread callback to change things, you'd think
that "find" is a read only operation. Suggestion for subsequent
cleanup: I think this can be made simple by using basic_safe_iterator.
>
> @@ -120,7 +123,23 @@ find_thread (Func func)
> cur = next;
> }
>
> - return NULL;
> + return nullptr;
> +}
> +
> +/* Like the above, but consider all threads of all processes. */
I would say just "consider threads of all processes".
> +
> +template <typename Func>
> +static thread_info *
> +find_thread (Func func)
> +{
> + for (process_info *proc : all_processes)
> + {
> + thread_info *thread = find_thread (proc, func);
Should this function (and maybe the other find_thread) take
`Func &&func`, and should this find_thread std::forward it to the other
find_thread (perfect forwarding)?
I'm not too sure why this code uses templating anyway, instead of
gdb::function_view, this seems like an overkill use of templates...
> @@ -142,21 +162,39 @@ template <typename Func>
> static thread_info *
> find_thread (ptid_t filter, Func func)
> {
> - return find_thread ([&] (thread_info *thread) {
> - return thread->id.matches (filter) && func (thread);
> - });
> + if (filter == minus_one_ptid)
> + return find_thread (func);
> +
> + process_info *process = find_process_pid (filter.pid ());
> + if (process == nullptr)
> + return nullptr;
> +
> + if (filter.is_pid ())
> + return find_thread (process, func);
> +
> + std::unordered_map<ptid_t, thread_info *> *thread_map
> + = get_thread_map (process);
> + std::unordered_map<ptid_t, thread_info *>::iterator it
> + = thread_map->find (filter);
> + if (it != thread_map->end () && func (it->second))
> + return it->second;
> +
> + return nullptr;
> }
>
> -/* Invoke FUNC for each thread. */
> +/* Invoke FUNC for each thread in the thread list of PROCESS. */
I would suggest "for each thread of PROCESS", to be more concise.
> @@ -41,8 +40,15 @@ struct thread_info *
> add_thread (ptid_t thread_id, void *target_data)
> {
> thread_info *new_thread = new thread_info (thread_id, target_data);
> + process_info *process = get_thread_process (new_thread);
>
> - all_threads.push_back (new_thread);
> + gdb_assert (process != nullptr);
> + /* A thread with this ptid should not exist in the map yet. */
> + gdb_assert (process->m_ptid_thread_map.find (thread_id)
> + == process->m_ptid_thread_map.end ());
Please add a blank line before the comment.
> diff --git a/gdbserver/inferiors.h b/gdbserver/inferiors.h
> index 00e42335014..289954412fe 100644
> --- a/gdbserver/inferiors.h
> +++ b/gdbserver/inferiors.h
> @@ -73,6 +73,13 @@ struct process_info
> /* DLLs that are loaded for this proc. */
> std::list<dll_info> all_dlls;
>
> + /* This processes' thread list, sorted by creation order. */
Just wondering, is the "sorted by creation order" aspect important to
any part of gdbserver?
> + std::list<thread_info *> m_thread_list;
Could you try to use gdb::intrusive_list for this? It could be as a
follow-up patch, or in this patch, I don't mind. It will be a bit more
lightweight than `std::list`. I think that most uses of std::list in
gdbserver could be replaced with gdb::intrusive_list (or
gdb::owning_intrusive_list).
> +
> + /* A map of ptid_t to thread_info*, for average O(1) ptid_t lookup.
> + Exited threads do not appear in the map. */
> + std::unordered_map<ptid_t, thread_info *> m_ptid_thread_map;
I'm wondering if we could just have the map, and not the list. The
"downside" is that it won't be sorted by creation order, which is why
I'm asking above.
> @@ -92,9 +99,25 @@ pid_of (const process_info *proc)
> return proc->pid;
> }
>
> -/* Return a pointer to the process that corresponds to the current
> - thread (current_thread). It is an error to call this if there is
> - no current thread selected. */
> +/* Accessors for a processes' thread list and map. */
> +
> +inline std::list<thread_info *> *
> +get_thread_list (struct process_info *process)
> +{
> + gdb_assert (process != nullptr);
> + return &(process->m_thread_list);
> +}
> +
> +inline std::unordered_map<ptid_t, thread_info *> *
> +get_thread_map (struct process_info *process)
> +{
> + gdb_assert (process != nullptr);
> + return &(process->m_ptid_thread_map);
> +}
Can these be methods of process_info instead? For getter methods, we usually
omit the `get_` prefix. You could then make the `m_*` fields actually
private.
> +
> +/* Return a pointer to the current process. Note that the current
> + process may be non-null while the current thread (current_thread)
> + is null. */
Is this comment change related to this patch? If not, put it in a
separate patch.
> @@ -2509,8 +2506,48 @@ static void
> handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
> {
> client_state &cs = get_client_state ();
> + static std::list<process_info *>::const_iterator process_iter;
> static std::list<thread_info *>::const_iterator thread_iter;
>
> + auto init_thread_iter = [&] ()
> + {
> + process_iter = all_processes.begin ();
> + std::list<thread_info *> *thread_list;
> +
> + for (; process_iter != all_processes.end (); ++process_iter)
> + {
> + thread_list = get_thread_list (*process_iter);
> + thread_iter = thread_list->begin ();
> + if (thread_iter != thread_list->end ())
> + break;
> + }
> + /* Make sure that there is at least one thread to iterate. */
> + gdb_assert (process_iter != all_processes.end ());
> + gdb_assert (thread_iter != thread_list->end ());
> + };
> +
> + auto advance_thread_iter = [&] ()
> + {
> + /* The loop below is written in the natural way as-if we'd always
> + start at the beginning of the inferior list. This fast forwards
> + the algorithm to the actual current position. */
I was like... this algorithm reminds me of gdb/thread-iter.h... oh, this
comment is from gdb/thread-iter.h :).
I saw Andrew's comment on v1, about sharing this algorithm in
gdbsupport. I agree on the principle, but it doesn't sound easy.
Simon
More information about the Gdb-patches
mailing list