forked from Imagelibrary/binutils-gdb
I'm investigating some issues where GDB takes a lot of time to start
up (read: for the DWARF index to be ready to do anything useful).
Adding those scoped_time_it instances helped me gain some insights about
where GDB spends time. I think they would be useful to have upstream,
to make investigating future problems easier. It would also be useful
to be able to give some numbers in the commit messages.
Here's an example of what GDB outputs:
Time for "minsyms install worker": wall 0.045, user 0.040, sys 0.004, user+sys 0.044, 97.8 % CPU
Time for "minsyms install worker": wall 0.511, user 0.457, sys 0.048, user+sys 0.505, 98.8 % CPU
Time for "minsyms install worker": wall 1.513, user 1.389, sys 0.111, user+sys 1.500, 99.1 % CPU
Time for "minsyms install worker": wall 1.688, user 1.451, sys 0.102, user+sys 1.553, 92.0 % CPU
Time for "minsyms install worker": wall 1.897, user 1.518, sys 0.089, user+sys 1.607, 84.7 % CPU
Time for "minsyms install worker": wall 2.811, user 2.558, sys 0.231, user+sys 2.789, 99.2 % CPU
Time for "minsyms install worker": wall 3.257, user 3.049, sys 0.188, user+sys 3.237, 99.4 % CPU
Time for "minsyms install worker": wall 3.617, user 3.089, sys 0.211, user+sys 3.300, 91.2 % CPU
Time for "DWARF indexing worker": wall 19.517, user 0.894, sys 0.075, user+sys 0.969, 5.0 % CPU
Time for "DWARF indexing worker": wall 19.807, user 0.891, sys 0.086, user+sys 0.977, 4.9 % CPU
Time for "DWARF indexing worker": wall 20.270, user 1.559, sys 0.119, user+sys 1.678, 8.3 % CPU
Time for "DWARF indexing worker": wall 20.329, user 1.878, sys 0.147, user+sys 2.025, 10.0 % CPU
Time for "DWARF indexing worker": wall 20.848, user 3.483, sys 0.224, user+sys 3.707, 17.8 % CPU
Time for "DWARF indexing worker": wall 21.088, user 4.285, sys 0.295, user+sys 4.580, 21.7 % CPU
Time for "DWARF indexing worker": wall 21.109, user 4.501, sys 0.274, user+sys 4.775, 22.6 % CPU
Time for "DWARF indexing worker": wall 21.198, user 5.087, sys 0.319, user+sys 5.406, 25.5 % CPU
Time for "DWARF skeletonless type units": wall 4.024, user 3.858, sys 0.115, user+sys 3.973, 98.7 % CPU
Time for "DWARF add parent map": wall 0.092, user 0.086, sys 0.004, user+sys 0.090, 97.8 % CPU
Time for "DWARF finalize worker": wall 0.278, user 0.241, sys 0.009, user+sys 0.250, 89.9 % CPU
Time for "DWARF finalize worker": wall 0.307, user 0.304, sys 0.000, user+sys 0.304, 99.0 % CPU
Time for "DWARF finalize worker": wall 0.727, user 0.719, sys 0.000, user+sys 0.719, 98.9 % CPU
Time for "DWARF finalize worker": wall 0.913, user 0.901, sys 0.003, user+sys 0.904, 99.0 % CPU
Time for "DWARF finalize worker": wall 0.776, user 0.767, sys 0.004, user+sys 0.771, 99.4 % CPU
Time for "DWARF finalize worker": wall 1.897, user 1.869, sys 0.006, user+sys 1.875, 98.8 % CPU
Time for "DWARF finalize worker": wall 2.534, user 2.512, sys 0.005, user+sys 2.517, 99.3 % CPU
Time for "DWARF finalize worker": wall 2.607, user 2.583, sys 0.006, user+sys 2.589, 99.3 % CPU
Time for "DWARF finalize worker": wall 3.142, user 3.094, sys 0.022, user+sys 3.116, 99.2 % CPU
Change-Id: I9453589b9005c3226499428ae9cab9f4a8c22904
Approved-By: Tom Tromey <tom@tromey.com>
261 lines
6.6 KiB
C
261 lines
6.6 KiB
C
/* DWARF index storage
|
|
|
|
Copyright (C) 2022-2025 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "dwarf2/cooked-index-worker.h"
|
|
#include "dwarf2/cooked-index.h"
|
|
#include "gdbsupport/thread-pool.h"
|
|
#include "maint.h"
|
|
#include "run-on-main-thread.h"
|
|
#include "event-top.h"
|
|
#include "exceptions.h"
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
cooked_index_worker_result::cooked_index_worker_result ()
|
|
: m_shard (new cooked_index_shard)
|
|
{
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
cutu_reader *
|
|
cooked_index_worker_result::get_reader (dwarf2_per_cu *per_cu)
|
|
{
|
|
auto it = m_reader_hash.find (*per_cu);
|
|
return it != m_reader_hash.end () ? it->get () : nullptr;
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
cutu_reader *
|
|
cooked_index_worker_result::preserve (cutu_reader_up reader)
|
|
{
|
|
m_abbrev_table_cache.add (reader->release_abbrev_table ());
|
|
|
|
auto [it, inserted] = m_reader_hash.insert (std::move (reader));
|
|
gdb_assert (inserted);
|
|
|
|
return it->get();
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
std::uint64_t
|
|
cooked_index_worker_result::cutu_reader_hash::operator()
|
|
(const cutu_reader_up &reader) const noexcept
|
|
{
|
|
return (*this) (*reader->cu ()->per_cu);
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
std::uint64_t
|
|
cooked_index_worker_result::cutu_reader_hash::operator() (const dwarf2_per_cu &per_cu)
|
|
const noexcept
|
|
{
|
|
return per_cu.index;
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
bool
|
|
cooked_index_worker_result::cutu_reader_eq::operator() (const cutu_reader_up &a,
|
|
const cutu_reader_up &b) const noexcept
|
|
{
|
|
return (*this) (*a->cu ()->per_cu, b);
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
bool cooked_index_worker_result::cutu_reader_eq::operator()
|
|
(const dwarf2_per_cu &per_cu, const cutu_reader_up &reader) const noexcept
|
|
{
|
|
return per_cu.index == reader->cu ()->per_cu->index;
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
void
|
|
cooked_index_worker_result::emit_complaints_and_exceptions
|
|
(gdb::unordered_set<gdb_exception> &seen_exceptions)
|
|
{
|
|
gdb_assert (is_main_thread ());
|
|
|
|
re_emit_complaints (m_complaints);
|
|
|
|
/* Only show a given exception a single time. */
|
|
for (auto &one_exc : m_exceptions)
|
|
if (seen_exceptions.insert (one_exc).second)
|
|
exception_print (gdb_stderr, one_exc);
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
void
|
|
cooked_index_worker::start ()
|
|
{
|
|
gdb::thread_pool::g_thread_pool->post_task ([this] ()
|
|
{
|
|
try
|
|
{
|
|
do_reading ();
|
|
}
|
|
catch (const gdb_exception &exc)
|
|
{
|
|
m_failed = exc;
|
|
set (cooked_state::CACHE_DONE);
|
|
}
|
|
|
|
bfd_thread_cleanup ();
|
|
});
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
bool
|
|
cooked_index_worker::wait (cooked_state desired_state, bool allow_quit)
|
|
{
|
|
bool done;
|
|
#if CXX_STD_THREAD
|
|
{
|
|
std::unique_lock<std::mutex> lock (m_mutex);
|
|
|
|
/* This may be called from a non-main thread -- this functionality
|
|
is needed for the index cache -- but in this case we require
|
|
that the desired state already have been attained. */
|
|
gdb_assert (is_main_thread () || desired_state <= m_state);
|
|
|
|
while (desired_state > m_state)
|
|
{
|
|
if (allow_quit)
|
|
{
|
|
std::chrono::milliseconds duration { 15 };
|
|
if (m_cond.wait_for (lock, duration) == std::cv_status::timeout)
|
|
QUIT;
|
|
}
|
|
else
|
|
m_cond.wait (lock);
|
|
}
|
|
done = m_state == cooked_state::CACHE_DONE;
|
|
}
|
|
#else
|
|
/* Without threads, all the work is done immediately on the main
|
|
thread, and there is never anything to wait for. */
|
|
done = desired_state == cooked_state::CACHE_DONE;
|
|
#endif /* CXX_STD_THREAD */
|
|
|
|
/* Only the main thread is allowed to report complaints and the
|
|
like. */
|
|
if (!is_main_thread ())
|
|
return false;
|
|
|
|
if (m_reported)
|
|
return done;
|
|
m_reported = true;
|
|
|
|
/* Emit warnings first, maybe they were emitted before an exception
|
|
(if any) was thrown. */
|
|
m_warnings.emit ();
|
|
|
|
if (m_failed.has_value ())
|
|
{
|
|
/* do_reading failed -- report it. */
|
|
exception_print (gdb_stderr, *m_failed);
|
|
m_failed.reset ();
|
|
return done;
|
|
}
|
|
|
|
/* Only show a given exception a single time. */
|
|
gdb::unordered_set<gdb_exception> seen_exceptions;
|
|
for (auto &one_result : m_results)
|
|
one_result.emit_complaints_and_exceptions (seen_exceptions);
|
|
|
|
print_stats ();
|
|
|
|
struct objfile *objfile = m_per_objfile->objfile;
|
|
dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
|
|
cooked_index *table
|
|
= (gdb::checked_static_cast<cooked_index *>
|
|
(per_bfd->index_table.get ()));
|
|
|
|
auto_obstack temp_storage;
|
|
enum language lang = language_unknown;
|
|
const char *main_name = table->get_main_name (&temp_storage, &lang);
|
|
if (main_name != nullptr)
|
|
set_objfile_main_name (objfile, main_name, lang);
|
|
|
|
/* dwarf_read_debug_printf ("Done building psymtabs of %s", */
|
|
/* objfile_name (objfile)); */
|
|
|
|
return done;
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
void
|
|
cooked_index_worker::set (cooked_state desired_state)
|
|
{
|
|
gdb_assert (desired_state != cooked_state::INITIAL);
|
|
|
|
#if CXX_STD_THREAD
|
|
std::lock_guard<std::mutex> guard (m_mutex);
|
|
gdb_assert (desired_state > m_state);
|
|
m_state = desired_state;
|
|
m_cond.notify_one ();
|
|
#else
|
|
/* Without threads, all the work is done immediately on the main
|
|
thread, and there is never anything to do. */
|
|
#endif /* CXX_STD_THREAD */
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
void
|
|
cooked_index_worker::write_to_cache (const cooked_index *idx)
|
|
{
|
|
if (idx != nullptr)
|
|
{
|
|
/* Writing to the index cache may cause a warning to be emitted.
|
|
See PR symtab/30837. This arranges to capture all such
|
|
warnings. This is safe because we know the deferred_warnings
|
|
object isn't in use by any other thread at this point. */
|
|
scoped_restore_warning_hook defer (&m_warnings);
|
|
m_cache_store.store ();
|
|
}
|
|
}
|
|
|
|
/* See cooked-index-worker.h. */
|
|
|
|
void
|
|
cooked_index_worker::done_reading ()
|
|
{
|
|
{
|
|
scoped_time_it time_it ("DWARF add parent map");
|
|
|
|
for (auto &one_result : m_results)
|
|
m_all_parents_map.add_map (*one_result.get_parent_map ());
|
|
}
|
|
|
|
dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
|
|
cooked_index *table
|
|
= (gdb::checked_static_cast<cooked_index *>
|
|
(per_bfd->index_table.get ()));
|
|
table->set_contents ();
|
|
}
|